Skip to content

Conversation

@jack-arturo
Copy link
Member

Summary

Adds detailed event data to SSE stream for better real-time observability of memory operations.

STORE events now include:

  • Full content (not truncated)
  • All tags and metadata
  • Type confidence, embedding/qdrant status

RECALL events now include:

  • Full query text
  • Aggregate stats (avg_length, avg_tags, score_range)
  • Top 3 result summaries with type/score/length/tags
  • Dedup removed count

ENRICHMENT events now include:

  • Entity counts by category (tools, people, projects, concepts, organizations)
  • Relationship counts (temporal, semantic, patterns)
  • Entity tags added count
  • Summary preview (80 chars)

Client improvements:

  • Simplified automem_watch.py to tail-style streaming log format
  • Added reconnect counter display
  • Enhanced display for all event types

Breaking change:

  • enrich_memory() now returns Dict[str, Any] instead of bool
  • Return dict includes: processed, entities, temporal_links, patterns_detected, semantic_neighbors, summary, entity_tags_added

Test plan

  • Unit tests pass
  • Run python scripts/automem_watch.py --url http://localhost:8001 --token dev
  • Store a memory via MCP - verify full content displays
  • Recall memories - verify aggregate stats + top 3 results display
  • Wait for enrichment - verify entity/relationship counts display
  • Test reconnect counter by killing server temporarily

🤖 Generated with Claude Code

STORE events now include:
- Full content (not truncated)
- All tags and metadata
- Type confidence, embedding/qdrant status

RECALL events now include:
- Full query text
- Aggregate stats (avg length, avg tags, score range)
- Top 3 result summaries with type/score/length/tags
- Dedup count

ENRICHMENT events now include:
- Entity counts by category (tools, people, projects, etc.)
- Relationship counts (temporal, semantic, patterns)
- Entity tags added count
- Summary preview

Also:
- Simplified automem_watch.py to tail-style streaming log
- Added reconnect counter display
- Refactored enrich_memory() to return detailed dict instead of bool

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Web-based monitor UI with authenticated real-time event streaming, history and log-status endpoints
    • Richer enrichment outputs: temporal links, entities, patterns, summaries, and skip reporting
    • Memory recall returns detailed result summaries and aggregate stats
    • Event logging/history and per-event SSE endpoints; memory association events emitted
    • CORS enabled for API
  • Improvements

    • Lightweight console stream viewer with structured event printing
  • Tests

    • Enrichment tests updated to expect richer result payloads (processed flag, temporal_links as list)

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds real-time event streaming, JSONL event logging, a static monitor UI and SSE endpoints, enrich/recall APIs emitting richer payloads and events, a console stream-watcher rewrite, and CORS support; enrich/temporal APIs and emitted event schemas now return and include richer structured metadata.

Changes

Cohort / File(s) Summary
Core App & Enrichment
app.py
Enriched enrich_memory() now returns Dict[str, Any] (processed, content, tags_before/after, tags_added, entities, temporal_links as IDs, semantic_neighbors, patterns_detected, summary, skip_reason). find_temporal_relationships() now returns List[str]. Added monitor_page() route and CORS initialization. Enrichment stats extended (skips, last_skip, record_skip).
Event Stream & Logging
automem/api/stream.py
Added JSONL event log (gated by AUTOMEM_EVENT_LOG) with thread-safe write/truncate; _get_event_log_path, _event_log_max, _event_log_lock, _write_event_to_log(). emit_event() writes to log and SSE subscribers. New public APIs get_event_history(limit) and get_log_status(). New endpoints /stream/history and /stream/log-status.
Memory APIs Instrumentation
automem/api/memory.py, automem/api/recall.py
Memory store/associate flows now call emit_event() (memory.store, memory.associate). Recall flow measures latency, builds result_summaries and aggregates (avg_length, avg_tags, score_range, dedup_removed), emits memory.recall with stats.
Monitoring UI & Watcher
static/monitor.html, scripts/automem_watch.py
Added full-featured monitor.html (SSE auth, /stream connection, history hydration, health/status cards, filters, event grid). Rewrote scripts/automem_watch.py from Rich TUI to streaming console output with event-specific printers and resilient SSE reconnection.
Tests
tests/test_enrichment.py
Updated test expectations: enrich_memory() returns dict; assert result.get("processed") is True and treat temporal_links as list (len(...) == 1).
Docs & Config
CLAUDE.md, requirements.txt
Documented new SSE endpoints and env vars (AUTOMEM_EVENT_LOG, AUTOMEM_EVENT_LOG_MAX). Added flask-cors==6.0.0 and integrated CORS in app.

Sequence Diagram(s)

sequenceDiagram
    participant App as App (enrich_memory)
    participant Stream as Stream API (emit_event)
    participant Log as Event Log (JSONL)
    participant SSE as SSE Subscribers
    participant Monitor as Monitor UI / automem_watch

    App->>Stream: emit enrichment_complete / memory.store / memory.recall events
    Stream->>Log: _write_event_to_log(event) (if enabled)
    Stream->>SSE: dispatch event to in-memory subscribers
    Monitor->>Stream: GET /stream (SSE handshake) & /stream/history
    Stream-->>Monitor: SSE messages (live) and /stream/history response (cached events)
    Monitor->>Monitor: render events / update UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately reflects the main changes: enhanced SSE monitoring with detailed event data across store, recall, and enrichment events, plus improvements to the watch client.
Description check ✅ Passed The PR description is well-related to the changeset, detailing specific event payload enhancements (STORE, RECALL, ENRICHMENT), client improvements, and the breaking change to enrich_memory() return type.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/enhanced-sse-monitoring

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

@coderabbitai coderabbitai bot added the enhancement New feature or request label Jan 9, 2026
Emit SSE events when memories are associated, showing:
- Source and target memory IDs
- Relation type (RELATES_TO, LEADS_TO, etc.)
- Association strength

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/automem_watch.py (1)

279-310: Consider adding a read timeout for connection resilience.

While timeout=None is appropriate for long-lived SSE connections (the static analysis hint is a false positive in this context), consider using httpx.Timeout with a longer read timeout to detect stale connections where the server stops sending even keepalives:

timeout = httpx.Timeout(connect=10.0, read=60.0, write=10.0, pool=10.0)
with httpx.Client(timeout=timeout) as client:

This allows the connection to stay open for streaming but will reconnect if no data (including keepalives) is received for 60 seconds.

The broad except Exception on line 306 is acceptable for a CLI reconnection loop since we want to retry on any transient network error.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe9902d and c30abcd.

📒 Files selected for processing (2)
  • app.py
  • scripts/automem_watch.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • app.py
  • scripts/automem_watch.py
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • app.py
🧬 Code graph analysis (1)
scripts/automem_watch.py (1)
automem/api/stream.py (1)
  • stream (87-102)
🪛 Ruff (0.14.10)
app.py

3013-3013: Do not catch blind exception: Exception

(BLE001)

scripts/automem_watch.py

281-281: Probable use of httpx call with timeout set to None

(S113)


306-306: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (11)
app.py (5)

1756-1784: LGTM!

The enrichment worker correctly adapts to the new enrich_memory return signature. The entity counts extraction, skip detection, and event emission with the new detailed fields are well-structured.


2088-2229: Breaking change handled well.

The signature change from bool to Dict[str, Any] is documented in the PR objectives. All code paths return a consistent dict structure with processed and either reason (for skipped cases) or enrichment details. The return schema matches the docstring.


2705-2717: Verify: Full content in SSE events may impact performance and privacy.

The content and metadata fields are now emitted in full. For large memories, this could create substantial SSE payloads. Additionally, if memory content contains sensitive data, it will be streamed to all connected SSE clients.

Consider whether truncation (similar to summary_preview in enrichment events) would be more appropriate, or if this is intentional for debugging purposes.


2990-3046: LGTM!

The enhanced recall event payload with aggregate stats and result summaries provides good observability. The result summaries are correctly limited (10 for processing, 3 for display). The division operations are safe due to the if result_summaries: guard.

The broad except Exception on line 3013 is acceptable here as it's defensive parsing of response data with debug-level logging, ensuring SSE emission doesn't fail if response parsing unexpectedly fails.


3126-3137: LGTM!

The new memory.associate SSE event emission is well-structured, providing the essential association details (both memory IDs, relation type, and strength) for real-time monitoring. This aligns with the commit message about adding SSE logging for memory.associate events.

scripts/automem_watch.py (6)

27-31: LGTM!

Simple timestamp formatting utility that correctly extracts the time portion from ISO timestamps.


34-81: LGTM!

Well-structured event printer with good formatting choices: truncated memory IDs for readability, metadata preview with overflow indicator, and content wrapping. Aligns well with the enhanced SSE payloads from app.py.


137-190: LGTM!

Comprehensive enrichment event handling that correctly maps to all three event subtypes (start, complete, failed) with appropriate detail levels. The display of entities, links, and summary preview aligns with the enhanced payloads from app.py.


193-247: LGTM!

Clean implementations for consolidation, error, associate, and raw event printers. The print_raw_event fallback with JSON syntax highlighting provides good visibility for any unhandled event types.


250-267: LGTM!

Clean event dispatcher with appropriate routing for all event types. The use of startswith("enrichment.") elegantly handles all enrichment subtypes with a single handler.


313-327: LGTM!

Clean entry point with proper argument handling and URL normalization.

enrich_memory() now returns a dict instead of bool, so check
result.get("processed") instead of direct bool comparison.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/test_enrichment.py (1)

111-112: Expand test coverage to validate the new return value fields.

The changes correctly adapt to the new enrich_memory return type. However, the test only validates result.get("processed") and doesn't check the other fields now returned (entities, temporal_links, patterns_detected, semantic_neighbors, summary, entity_tags_added).

Consider adding assertions to validate these return value fields directly, especially since lines 119-124 already validate the same data via side effects and metadata inspection.

🧪 Suggested test enhancement
     result = app.enrich_memory("mem-1", forced=True)
     assert result.get("processed") is True
+    assert "projects" in result.get("entities", {})
+    assert "Launchpad" in result["entities"]["projects"]
+    assert result.get("temporal_links", 0) >= 1
+    assert result.get("patterns_detected", [])
+    assert isinstance(result.get("summary", ""), str)
+    assert result.get("entity_tags_added", 0) >= 1
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c30abcd and e582888.

📒 Files selected for processing (1)
  • tests/test_enrichment.py
🧰 Additional context used
📓 Path-based instructions (3)
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Set environment variable PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 when running pytest to disable conflicting plugins
Use pytest with DummyGraph fixture to mock FalkorDB operations in unit tests

Files:

  • tests/test_enrichment.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • tests/test_enrichment.py
tests/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/test_*.py: Place tests in the tests/ directory with filenames matching test_*.py
Use Pytest as the testing framework with fixtures preferred over global variables
Integration tests should be placed in tests/ and run with make test-integration using Docker

Files:

  • tests/test_enrichment.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline

Applied to files:

  • tests/test_enrichment.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`

Applied to files:

  • tests/test_enrichment.py
🧬 Code graph analysis (1)
tests/test_enrichment.py (1)
app.py (1)
  • enrich_memory (2088-2229)

@railway-app railway-app bot temporarily deployed to automem / production January 9, 2026 19:42 Inactive
Enrichment events now include:
- Full memory content
- Tags before/after enrichment (with delta)
- Entities by category (tools, people, projects, etc.)
- Temporal link IDs (not just count)
- Semantic neighbor IDs with similarity scores
- Pattern detection details
- Generated summary

Changes:
- enrich_memory() now returns detailed dict with before/after state
- find_temporal_relationships() returns List[str] instead of count
- automem_watch.py displays enhanced enrichment output

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 9, 2026 20:11 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
app.py (5)

1726-1814: Enrichment “success” stats count skips as successes (metrics skew).

state.enrichment_stats.record_success(...) runs even when result["processed"] is false (skipped), so “successes” will include “already_processed/memory_not_found” cases. Consider splitting “completed” vs “processed” (or only increment successes when processed=True).

Proposed fix
-            try:
-                result = enrich_memory(job.memory_id, forced=job.forced)
-                state.enrichment_stats.record_success(job.memory_id)
+            try:
+                result = enrich_memory(job.memory_id, forced=job.forced)
+                if result.get("processed"):
+                    state.enrichment_stats.record_success(job.memory_id)
                 elapsed_ms = int((time.perf_counter() - enrich_start) * 1000)

2087-2240: Don’t truncate IDs in enrich_memory() return payload (truncate only in display).

semantic_neighbors is returned as [(nid[:8], ...)], which limits downstream consumers and conflicts with the idea of “detailed event data”. Since scripts/automem_watch.py already formats output, I’d keep full IDs in the returned dict/SSE payload and let the watch client truncate for printing. Based on learnings, entity tags format looks correct (entity:<type>:<slug>).

Proposed fix
     return {
         "processed": True,
         "content": content,
         "tags_before": original_tags,
         "tags_after": tags,
         "tags_added": tags_added,
         "entities": entities,
         "temporal_links": temporal_link_ids,
-        "semantic_neighbors": [(nid[:8], round(score, 3)) for nid, score in semantic_neighbors],
+        "semantic_neighbors": [(nid, round(score, 3)) for nid, score in semantic_neighbors],
         "patterns_detected": pattern_info,
         "summary": (summary or ""),
     }

2242-2283: Temporal link query can emit duplicates; prefer DISTINCT (or dedup in Python).

RETURN m2.id without DISTINCT can produce duplicate linked_ids if the store ever allows duplicate IDs in the result set for any reason (or future query changes). MERGE protects the graph edge, but the returned list may include dupes and inflate counts in SSE/test expectations.

Proposed fix
             """
             MATCH (m1:Memory {id: $id})
             MATCH (m2:Memory)
             WHERE m2.id <> $id
                 AND m2.timestamp IS NOT NULL
                 AND m1.timestamp IS NOT NULL
                 AND m2.timestamp < m1.timestamp
-            RETURN m2.id
+            RETURN DISTINCT m2.id
             ORDER BY m2.timestamp DESC
             LIMIT $limit
             """,

3000-3060: Avoid broad Exception catch; also aggregate stats currently computed from top-10 summaries only.

  • The try/except Exception around response parsing is flagged (BLE001) and can mask real regressions; narrow it to parsing-related exceptions (e.g., TypeError, ValueError, KeyError) and keep a small debug message.
  • avg_length/avg_tags/score_range are computed from result_summaries (top 10), not all results; if the intent is “aggregate stats”, compute from full results when available.
Proposed narrowing of exception handling
-    except Exception as e:
-        logger.debug("Failed to parse response for result_count", exc_info=e)
+    except (TypeError, ValueError, KeyError) as e:
+        logger.debug("Failed to parse response for result_count", exc_info=e)

2714-2730: SSE memory.store now broadcasts full content + metadata without filtering: data exfiltration risk.

The /stream endpoint uses identical token auth as regular API endpoints—no separate restriction. Since emit_event() includes data payloads unfiltered, any client with the API token can stream full memory content and metadata in real time. Consider:

  • Separate "monitoring token" with read-only stream access
  • Environment variable to limit payload scope (e.g., exclude full content/metadata)
  • Redaction function to strip known sensitive metadata keys before emission
🤖 Fix all issues with AI agents
In @scripts/automem_watch.py:
- Around line 34-82: In print_store_event, guard against non-string tags and
huge metadata values by coercing each tag to str before joining (e.g., map str
over tags used in the ", ".join) and by building metadata preview from truncated
stringified values: for each (k,v) in metadata.items() create a safe_val =
str(v)[:N] (append ellipsis if truncated) and for non-scalar or nested values
consider using a short summary (like type(v) or repr(v) truncated) before
joining into meta_preview; keep the existing limit of 5 entries and update
meta_preview construction and the tags handling accordingly.
- Around line 84-135: In print_recall_event, the stats handling assumes
score_range is a two-element numeric sequence which can raise on
missing/short/non-numeric values; update the code that uses score_range (the
variable named score_range in the stats block inside the print_recall_event
function) to defensively coerce and validate values (e.g., ensure
stats.get("score_range") yields at least two items, attempt float() on each
element in a try/except, and fall back to a safe representation such as "n/a" or
the raw value if conversion fails) and then use the safe/coerced values in the
formatted output instead of directly doing
f"{score_range[0]:.2f}-{score_range[1]:.2f}".
- Around line 303-344: The stream_events function currently constructs the HTTPX
client with timeout=None which disables all timeouts and can hang on dead
sockets; change the client creation (in the stream_events function where
httpx.Client(...) is used and the subsequent client.stream("GET",
f"{url}/stream", headers=headers) call) to use an explicit httpx.Timeout (e.g.,
httpx.Timeout(connect=10.0, read=None, write=10.0) if you want an infinite read
for SSE) so connect/write have finite timeouts while keeping read as needed;
update the httpx.Client(...) instantiation to pass that Timeout object and keep
the rest of the SSE loop and error handling unchanged.
🧹 Nitpick comments (2)
tests/test_enrichment.py (1)

111-113: Test update matches new enrich_memory() return contract (dict + processed).

This looks aligned with the new API, and the updated temporal assertion matches the new list return. Optional: add a skip-path test asserting {"processed": False, "reason": ...} for already-processed memories to lock in the contract. As per coding guidelines, consider migrating graph mocking to the project’s DummyGraph fixture if that’s the standard for FalkorDB-related tests.

Also applies to: 122-122

scripts/automem_watch.py (1)

27-31: format_timestamp() should accept Optional[str] and parse ISO more robustly.

Right now None (or non-ISO strings) can throw or format oddly. Consider using datetime.fromisoformat (with a fallback).

Proposed fix
+from datetime import datetime
+from typing import Optional
@@
-def format_timestamp(ts: str) -> str:
-    """Extract just the time from ISO timestamp."""
-    if "T" in ts:
-        return ts.split("T")[1][:12]
-    return ts[:12]
+def format_timestamp(ts: Optional[str]) -> str:
+    """Format an ISO timestamp as HH:MM:SS(.mmm) when possible."""
+    if not ts:
+        return ""
+    try:
+        parsed = datetime.fromisoformat(ts.replace("Z", "+00:00"))
+        return parsed.time().isoformat(timespec="milliseconds")
+    except ValueError:
+        # Fallback to previous heuristic
+        if "T" in ts:
+            return ts.split("T", 1)[1][:12]
+        return ts[:12]
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e582888 and 03a3eac.

📒 Files selected for processing (3)
  • app.py
  • scripts/automem_watch.py
  • tests/test_enrichment.py
🧰 Additional context used
📓 Path-based instructions (3)
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Set environment variable PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 when running pytest to disable conflicting plugins
Use pytest with DummyGraph fixture to mock FalkorDB operations in unit tests

Files:

  • tests/test_enrichment.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • tests/test_enrichment.py
  • scripts/automem_watch.py
  • app.py
tests/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/test_*.py: Place tests in the tests/ directory with filenames matching test_*.py
Use Pytest as the testing framework with fixtures preferred over global variables
Integration tests should be placed in tests/ and run with make test-integration using Docker

Files:

  • tests/test_enrichment.py
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`

Applied to files:

  • tests/test_enrichment.py
  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline

Applied to files:

  • tests/test_enrichment.py
  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • tests/test_enrichment.py
  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write

Applied to files:

  • tests/test_enrichment.py
  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)

Applied to files:

  • tests/test_enrichment.py
  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • app.py
🧬 Code graph analysis (2)
tests/test_enrichment.py (1)
app.py (1)
  • enrich_memory (2087-2239)
scripts/automem_watch.py (3)
tests/test_enrichment.py (1)
  • query (27-71)
automem/api/graph.py (1)
  • stats (362-452)
automem/api/stream.py (1)
  • stream (87-102)
🪛 Ruff (0.14.10)
scripts/automem_watch.py

314-314: Probable use of httpx call with timeout set to None

(S113)


339-339: Do not catch blind exception: Exception

(BLE001)

app.py

3026-3026: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (4)
app.py (1)

3139-3149: SSE memory.associate payload looks clean and display-friendly.

scripts/automem_watch.py (3)

283-301: Event routing is clear and easy to extend.


346-361: CLI entrypoint is simple and does what it needs to.


137-224: No changes needed. The unpacking of semantic_neighbors on line 200 is correct: the enrichment event streams semantic_neighbors as a list of tuples (nid, score) (from enrich_memory() return value), which matches the unpacking in the code. Additionally, no documentation confirms that the 80-character summary target applies to this output—the 80-character truncation is used only for the error field.

Likely an incorrect or invalid review comment.

Three critical bugs found in Railway production logs:

1. **DateTime TypeError (500s)**
   - `_parse_iso_datetime()` now assumes UTC for naive timestamps
   - Fixes: "can't compare offset-naive and offset-aware datetimes"

2. **OpenAI max_tokens 400 error**
   - Use `max_completion_tokens` for o-series/gpt-5 models
   - Use `max_tokens` for older models (gpt-4o-mini, etc.)

3. **Qdrant 404 race condition**
   - Handle UnexpectedResponse 404 gracefully (point not yet uploaded)
   - Log as debug instead of error for expected race condition

Added test coverage for naive datetime handling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 9, 2026 20:53 Inactive
Per CodeRabbit review:
- o-series (o1, o3, etc.): max_completion_tokens
- gpt-5 family: max_output_tokens
- Others (gpt-4o-mini, etc.): max_tokens

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 9, 2026 20:55 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
automem/utils/text.py (1)

147-166: Fix token parameter names for Chat Completions API compatibility.

The code uses incorrect token parameters for the Chat Completions endpoint:

  • gpt-5 family (line 154): Should use max_completion_tokens, not max_output_tokens. The max_output_tokens parameter is for the Responses API, not Chat Completions.
  • gpt-4/gpt-4o-mini (line 157): Should use max_completion_tokens, not the deprecated max_tokens. The max_tokens parameter has been replaced by max_completion_tokens in the Chat Completions API.

Update the logic to:

if model.startswith("o") or model.startswith("gpt-5"):  # o-series and gpt-5 family
    token_param = {"max_completion_tokens": token_limit}
else:  # gpt-4, gpt-4o-mini, and other models
    token_param = {"max_completion_tokens": token_limit}

Or simplify to use max_completion_tokens for all models via Chat Completions API.

🧹 Nitpick comments (1)
automem/utils/time.py (1)

25-29: LGTM with optional style improvement.

The logic correctly ensures all parsed timestamps are timezone-aware by treating naive timestamps as UTC, which aligns with the coding guidelines requiring UTC normalization.

💅 Optional: Address static analysis hint (TRY300)

The Ruff linter suggests moving the timezone assignment to an else block for clearer separation of the success path:

 try:
     dt = datetime.fromisoformat(candidate)
-    # Ensure timezone-aware (assume UTC if naive)
-    if dt.tzinfo is None:
-        dt = dt.replace(tzinfo=timezone.utc)
-    return dt
 except ValueError:
     return None
+else:
+    # Ensure timezone-aware (assume UTC if naive)
+    if dt.tzinfo is None:
+        dt = dt.replace(tzinfo=timezone.utc)
+    return dt

This is a stylistic preference and the current code is clear and correct.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03a3eac and ad425b5.

📒 Files selected for processing (4)
  • app.py
  • automem/utils/text.py
  • automem/utils/time.py
  • tests/test_app.py
🧰 Additional context used
📓 Path-based instructions (4)
automem/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

automem/**/*.py: Ensure graph writes always succeed even if vector storage fails by implementing graceful degradation in embedding and vector store operations
Normalize all timestamps to UTC ISO format for storage and retrieval
Implement graph operations as atomic transactions with automatic rollback on errors
Log vector store errors but do not block graph writes when vector storage fails

Files:

  • automem/utils/text.py
  • automem/utils/time.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • automem/utils/text.py
  • app.py
  • tests/test_app.py
  • automem/utils/time.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Set environment variable PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 when running pytest to disable conflicting plugins
Use pytest with DummyGraph fixture to mock FalkorDB operations in unit tests

Files:

  • tests/test_app.py
tests/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/test_*.py: Place tests in the tests/ directory with filenames matching test_*.py
Use Pytest as the testing framework with fixtures preferred over global variables
Integration tests should be placed in tests/ and run with make test-integration using Docker

Files:

  • tests/test_app.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Use Qdrant vector database with optional QDRANT_URL, QDRANT_API_KEY, and QDRANT_COLLECTION environment variables for semantic search, with graceful degradation when unavailable

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*.py : Normalize all timestamps to UTC ISO format for storage and retrieval

Applied to files:

  • tests/test_app.py
  • automem/utils/time.py
🧬 Code graph analysis (2)
app.py (3)
automem/api/stream.py (1)
  • emit_event (22-44)
automem/utils/time.py (1)
  • utc_now (7-9)
automem/utils/tags.py (1)
  • _normalize_tag_list (7-20)
tests/test_app.py (2)
automem/utils/time.py (1)
  • _parse_iso_datetime (12-31)
app.py (1)
  • _result_passes_filters (246-323)
🪛 Ruff (0.14.10)
app.py

3046-3046: Do not catch blind exception: Exception

(BLE001)

automem/utils/time.py

29-29: Consider moving this statement to an else block

(TRY300)

🔇 Additional comments (13)
tests/test_app.py (3)

365-379: LGTM!

Good defensive tests ensuring update_last_accessed handles edge cases gracefully (empty list and missing graph).


387-413: LGTM!

Comprehensive tests verifying the naive-to-UTC conversion behavior and preservation of explicit timezones. These align perfectly with the changes in automem/utils/time.py.


415-431: LGTM!

Excellent integration test verifying that filter comparison works correctly with mixed naive/aware timestamps, which was the root cause being fixed by the datetime handling changes.

app.py (10)

34-37: LGTM!

Good defensive import pattern that maintains test compatibility while enabling specific Qdrant error handling.


788-804: Verify token parameter names (same as utils/text.py).

This uses the same model-specific token parameter selection as automem/utils/text.py. Please verify the parameter names are correct for each model family (see comment on text.py).


1768-1795: LGTM!

The enrichment worker correctly consumes the new dict-based return value from enrich_memory, extracting all the expanded metadata fields for the SSE event payload.


2099-2136: LGTM!

The updated signature clearly documents the breaking change and the new dict-based return value. The early return cases (memory not found, already processed) correctly return dicts with processed: False and appropriate reasons.


2226-2234: Good handling of Qdrant race condition.

The 404 error handling correctly recognizes that the embedding upload may not have completed yet (async queue), logging at debug level instead of warning. This prevents noise in logs for expected race conditions.


2245-2259: LGTM!

The return dict correctly includes all documented fields and provides rich metadata for SSE event emission and observability.


2262-2302: LGTM!

Good refactor to return the list of linked memory IDs. This enables the caller to track and report which temporal relationships were created, supporting the expanded enrichment event payload.


2738-2750: LGTM!

The expanded SSE payload provides comprehensive observability with full content, all tags, complete metadata, and detailed status information as documented in the PR objectives.


3023-3079: LGTM!

Excellent expansion of the recall event payload with aggregate statistics, result summaries, and comprehensive metadata. The defensive exception handling at line 3046 is appropriate for parsing the response object safely, despite the static analysis hint.


3159-3168: LGTM!

Good addition of SSE event for association creation, providing observability for relationship operations.

@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 04:56 Inactive
Per CodeRabbit review:
- Coerce tags to strings before joining (handle non-string tags)
- Truncate large metadata values with ellipsis
- Validate score_range is 2-element numeric sequence, fallback to "n/a"
- Use explicit httpx.Timeout (connect=10s, read=None, write=10s)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 05:12 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @scripts/automem_watch.py:
- Around line 90-150: print_recall_event should defensively handle dirty payload
shapes: when building filters, check tags_filter with
isinstance(data.get("tags_filter"), (list, tuple, set)) before using len() and
fall back to a safe placeholder otherwise; for stats.score_range and top result
fields convert/validate numeric values before formatting (e.g., try to cast
score_range[0/1] to float, and for each top result use a safe_number =
_try_float(r.get("score")) and safe_int for tags_count/content_length, falling
back to "n/a" or 0) so that "{:.2f}" and len() never receive invalid types —
update handling for score_range, r.get('score'), r.get('tags_count'),
r.get('content_length'), and data['tags_filter'] inside print_recall_event
accordingly.
- Around line 34-88: In print_store_event, make ID and numeric fields defensive:
coerce mem_id via str(data.get("memory_id", "?")) before slicing (mem_id =
str(... )[:8]) to avoid TypeError on None/non-string; similarly ensure
type_conf, importance and elapsed are converted to numbers before formatting
(e.g., try cast type_conf = float(data.get("type_confidence", 0)) and fall back
to 0.0 on exception, importance = float(...) with default 0.5, elapsed =
int(...) or round(float(...)) with default 0) so the f-string formatting
({type_conf:.2f}, importance, elapsed) never errors; update references to
mem_id, type_conf, importance, and elapsed in the function accordingly.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad425b5 and 00012a2.

📒 Files selected for processing (1)
  • scripts/automem_watch.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • scripts/automem_watch.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • scripts/automem_watch.py
🪛 Ruff (0.14.10)
scripts/automem_watch.py

356-356: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (2)
scripts/automem_watch.py (2)

298-316: Event routing is clean and readable
The dedicated printers + process_event() dispatch makes this much easier to extend than a monolithic renderer.


363-378: CLI entrypoint looks good; rstrip('/') is the right touch
Makes the URL composition predictable and avoids //stream.

Per CodeRabbit review:
- Add _safe_float() and _safe_int() helpers for robust type conversion
- Coerce mem_id to string before slicing
- Safe conversions for type_conf, importance, elapsed in store events
- Safe conversions for result_count, dedup, elapsed in recall events
- isinstance check for tags_filter before len()
- Safe conversions for score, content_length, tags_count in result summaries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 05:32 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @scripts/automem_watch.py:
- Around line 292-293: The slices for memory IDs use data.get("memory1_id",
"?")[:8] and data.get("memory2_id", "?")[:8] which can fail if the values are
not strings; wrap the results with str() before slicing (e.g., mem1 =
str(data.get("memory1_id", "?"))[:8] and mem2 = str(data.get("memory2_id",
"?"))[:8]) to defensively handle non-string types, consistent with the approach
used earlier at the other memory ID extraction.
- Line 383: The function definition for main() lacks a return type hint; update
the signature of main (the main() function) to include an explicit return type
(e.g., def main() -> None:) consistent with project typing guidelines, and run
quick type-check/flake to ensure it satisfies the linter.
- Line 178: Replace the direct slice on the possibly non-string memory_id with a
defensive string conversion: change the assignment to use
str(data.get("memory_id", "?"))[:8] so that mem_id is safely created even if
memory_id is a UUID, int or other non-str type; update the mem_id assignment in
scripts/automem_watch.py (the line with mem_id = data.get("memory_id", "?")[:8])
to match the defensive pattern used earlier (line that currently uses str(...)).
- Around line 232-233: Temporal link IDs are sliced without ensuring they are
strings, which will fail for UUID or non-str types; update the list
comprehension that builds ids from temporal_links to defensively convert each
tid to a string before slicing (e.g., replace [tid[:8] for tid in
temporal_links] with [str(tid)[:8] for tid in temporal_links]) so it matches the
defensive conversion used earlier around line 178.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00012a2 and a621599.

📒 Files selected for processing (1)
  • scripts/automem_watch.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • scripts/automem_watch.py
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
📚 Learning: 2026-01-06T09:59:50.289Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T09:59:50.289Z
Learning: Applies to **/*.py : Python files must use type hints

Applied to files:

  • scripts/automem_watch.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • scripts/automem_watch.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • scripts/automem_watch.py
🪛 Ruff (0.14.10)
scripts/automem_watch.py

376-376: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (8)
scripts/automem_watch.py (8)

27-31: LGTM!

The timestamp formatting logic is clean and handles both ISO timestamps and fallback cases appropriately.


34-48: LGTM!

The defensive conversion helpers align well with the PR's objective of adding type-safe event data extraction. The two-step conversion int(float(val)) in _safe_int correctly handles both numeric strings and float values.


50-104: LGTM!

The defensive type conversions (str coercion on line 59, safe numeric conversions on lines 61-63, tag string coercion on lines 73-76) demonstrate robust error handling as intended by this PR. The metadata truncation logic is also well-implemented.


106-170: LGTM!

Excellent defensive programming with safe numeric conversions (lines 116-118), isinstance checks for tags_filter (line 129), and robust score_range handling (lines 146-152). This aligns perfectly with the PR's defensive handling improvements.


261-285: LGTM!

Both print_consolidation_event and print_error_event are well-implemented with proper type hints and safe data extraction.


304-336: LGTM!

The raw event printing with JSON syntax highlighting (line 313-315) and the event routing logic are well-implemented. The use of default=str in json.dumps is a good defensive practice.


338-381: LGTM - Broad exception handling is appropriate here.

The broad Exception catch on line 376 is intentional and appropriate for SSE reconnection logic, where any connection failure should trigger a reconnect attempt. The timeout configuration (line 350) correctly sets infinite read timeout for long-lived SSE connections while keeping finite connect/write timeouts.


235-236: The semantic_neighbors structure is correctly formatted for unpacking.

The enrich_memory() function returns semantic_neighbors as a list of 2-element tuples: [(nid[:8], round(score, 3)) for nid, score in semantic_neighbors] (app.py:2256). This structure matches the unpacking expectation in automem_watch.py:235-236 (for nid, score in semantic_neighbors), so no defensive unpacking is needed. The code is correct and will not raise a ValueError.

Likely an incorrect or invalid review comment.

Per CodeRabbit review:
- Wrap memory_id in enrichment events with str() before slicing
- Wrap temporal link IDs with str() before slicing
- Wrap memory1_id/memory2_id in associate events with str()
- Add return type hint to main() -> None

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 05:44 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @scripts/automem_watch.py:
- Around line 234-236: The code assumes every element in semantic_neighbors
unpacks to (nid, score) which can raise ValueError; in the block around
semantic_neighbors -> neighbor_strs and console.print, defensively unpack each
item (e.g., iterate items and try to unpack into nid, score, falling back to
treating the item as a single id or converting to a string if unpack fails) or
skip malformed entries, then build neighbor_strs from those safe nid/score
values before joining and printing; update the comprehension that creates
neighbor_strs in the semantic_neighbors handling to use this defensive unpacking
and avoid runtime exceptions.
🧹 Nitpick comments (2)
scripts/automem_watch.py (2)

287-302: Consider using safe conversion for strength field.

While data.get("strength", 0.5) provides a default for missing values, if the strength field contains a non-numeric type (e.g., a string), the f-string formatting at line 300 will display it as-is rather than converting to a float.

♻️ Optional: Use _safe_float for consistency
     mem1 = str(data.get("memory1_id", "?"))[:8]
     mem2 = str(data.get("memory2_id", "?"))[:8]
     rel_type = data.get("relation_type", "?")
-    strength = data.get("strength", 0.5)
+    strength = _safe_float(data.get("strength"), 0.5)
 
     console.print(
         f"[dim]{ts}[/] [bold blue]ASSOCIATE[/] "

376-380: Improve exception logging for debugging.

The broad exception handler ensures resilient reconnection, which is appropriate for a streaming client. However, the current implementation only displays the exception message, making it harder to debug unexpected errors.

♻️ Proposed enhancement to log exception type
         except Exception as e:
             reconnect_count += 1
-            console.print(f"[red]Connection error:[/] {e}")
+            console.print(f"[red]Connection error ({type(e).__name__}):[/] {e}")
             console.print(f"[dim]Reconnecting in 5s... (attempt #{reconnect_count})[/]")
             time.sleep(5)

This preserves the resilient reconnection behavior while providing better diagnostic information.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a621599 and 05fb0b4.

📒 Files selected for processing (1)
  • scripts/automem_watch.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • scripts/automem_watch.py
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
📚 Learning: 2026-01-06T09:59:50.289Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T09:59:50.289Z
Learning: Applies to **/*.py : Python files must use type hints

Applied to files:

  • scripts/automem_watch.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • scripts/automem_watch.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • scripts/automem_watch.py
🧬 Code graph analysis (1)
scripts/automem_watch.py (2)
automem/api/graph.py (1)
  • stats (362-452)
automem/api/stream.py (1)
  • stream (87-102)
🪛 Ruff (0.14.10)
scripts/automem_watch.py

376-376: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (7)
scripts/automem_watch.py (7)

9-24: LGTM!

The import structure is clean, with appropriate error handling for missing dependencies. The module-level Console instance is suitable for this streaming script.


27-48: LGTM!

The timestamp formatting and safe conversion helpers implement defensive coding practices, preventing type errors when processing event data with unexpected types.


50-104: LGTM!

The STORE event printer implements comprehensive defensive coding with safe type conversions for all fields. The content wrapping and metadata truncation ensure readable output even with large payloads.


106-170: LGTM!

The RECALL event printer handles edge cases well, with defensive type checking for tags_filter and robust score range formatting that gracefully handles malformed data.


261-276: LGTM!

The consolidation event printer uses safe dictionary access with appropriate defaults throughout.


304-336: LGTM!

The raw event printer and event router are well-structured. The use of Syntax for JSON formatting provides readable output for unhandled event types.


383-397: LGTM!

The main function includes the return type hint as specified in the PR objectives. The URL normalization and clear help text make the script user-friendly.

Addresses final CodeRabbit review comment about proper type hints.
All event handler functions now use Event = Mapping[str, Any] instead
of dict for better type safety with SSE payloads.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 06:02 Inactive
Handles malformed 2-element sequences in semantic_neighbors data
by using try/except with index access instead of tuple unpacking.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 06:27 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/auto-fix.yml:
- Around line 46-49: The "Setup Codex Auth" step writes the secret to
~/.codex/auth.json; instead, stop writing the secret to disk and pass the secret
as an environment variable to the Codex/consumer step (remove the mkdir/echo
lines), e.g., expose ${{ secrets.CODEX_AUTH_JSON }} via env for the job or the
specific step that runs the Codex action and update any downstream use to read
the credential from that environment variable (refer to the "Setup Codex Auth"
step name and the subsequent Codex action invocation to ensure the env var is
consumed).
🧹 Nitpick comments (1)
.github/workflows/auto-fix.yml (1)

115-122: Add validation and review steps before auto-committing changes.

The workflow commits and pushes changes directly without validation, which poses several risks:

  1. No change validation: Changes are committed without verifying they actually fix the issue or pass tests
  2. No human review: Direct push bypasses PR review, even for potentially breaking changes
  3. [skip ci] prevents validation: Changes aren't tested before merge
  4. No rollback mechanism: If auto-fix introduces new failures, there's no automated recovery

Consider these improvements:

  1. Run tests after Codex makes changes but before committing
  2. Create a PR instead of direct push for non-trivial changes
  3. Remove [skip ci] or use a separate validation workflow
  4. Add a comment to the original PR linking to the auto-fix commit
  5. Implement a maximum retry limit to prevent fix loops
🔒 Example: Create PR instead of direct push
-      - name: Commit and push
+      - name: Create fix branch and PR
         if: steps.changes.outputs.has_changes == 'true'
         run: |
+          FIX_BRANCH="autofix/${{ steps.branch.outputs.branch }}-$(date +%s)"
+          git checkout -b "$FIX_BRANCH"
           git config user.name "codex[bot]"
           git config user.email "codex[bot]@users.noreply.github.com"
           git add .
-          git commit -m "fix: auto-fix CI failures [skip ci]"
-          git push
+          git commit -m "fix: auto-fix CI failures"
+          git push origin "$FIX_BRANCH"
+          gh pr create --base "${{ steps.branch.outputs.branch }}" \
+            --head "$FIX_BRANCH" \
+            --title "🤖 Auto-fix CI failures" \
+            --body "Automated fixes generated by Codex for CI failures."
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f51551e and 59ea7c4.

📒 Files selected for processing (2)
  • .github/codex/prompts/fix-ci.md
  • .github/workflows/auto-fix.yml
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
🪛 LanguageTool
.github/codex/prompts/fix-ci.md

[style] ~16-~16: Consider using a different verb for a more formal wording.
Context: ...cal** - Only change what's necessary to fix the specific issue 2. *Don't refactor...

(FIX_RESOLVE)

🔇 Additional comments (4)
.github/codex/prompts/fix-ci.md (1)

1-36: Clear and well-structured CI fix guidance.

The documentation provides appropriate guardrails for automated fixes, emphasizing surgical changes and preserving existing patterns. The rules and cautions align well with the automated workflow's needs.

.github/workflows/auto-fix.yml (3)

78-95: Clean prompt preparation with sensible fallbacks.

The three-tier fallback strategy (custom input → repository prompt file → default) provides good flexibility, and the context appending logic is straightforward.


29-44: Correct branch determination logic.

The branch selection properly handles both workflow_dispatch (with optional input) and workflow_run triggers. Using fetch-depth: 0 is appropriate for ensuring Codex has access to full repository history if needed.


51-76: The --log-failed flag is valid and correctly used.

The GitHub CLI documentation confirms that gh run view --log-failed is a valid option that displays logs only for failed steps in a workflow run. The PR comment filtering syntax using gh pr view --json comments -q '.comments[] | select(.author.login == "coderabbitai")' also follows documented patterns correctly.

The code as written is functionally sound. The tail -200 limit is a reasonable truncation strategy, though it could be made configurable if longer context is needed for debugging.

Likely an incorrect or invalid review comment.

…dling

Current stream parsing only handles single-line data: frames; multi-line
data: events (valid per SSE RFC 8030) are dropped/corrupted. Ruff config
enables BLE001, which flags except Exception without re-raising—this can
cause infinite reconnect loops on programmer errors like AttributeError.
Non-200 responses don't differentiate between auth failures (401/403)
that shouldn't retry and transient errors that should.

Changes:
- Buffer multi-line data: frames per SSE RFC (join with newlines)
- Dispatch on empty line (end of event marker)
- Narrow exception handling to httpx.RequestError/HTTPStatusError
- Differentiate 401/403 (exit) from 5xx (retry with backoff)
- SSL-specific error detection with exponential backoff (Railway LB issue)
- Add --max-reconnects flag to limit reconnection attempts
- Fix misplaced malformed data logging (was under keepalive handler)
- Log malformed JSON event data for debugging

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 18:50 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/automem_watch.py (1)

161-163: Simplify string concatenation.

The implicit string concatenation can be merged into a single f-string for clarity.

Suggested fix
         console.print(
-            f"  [yellow]stats:[/] avg_len={avg_len} avg_tags={avg_tags} " f"score_range={score_str}"
+            f"  [yellow]stats:[/] avg_len={avg_len} avg_tags={avg_tags} score_range={score_str}"
         )
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59ea7c4 and 63f668a.

📒 Files selected for processing (1)
  • scripts/automem_watch.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • scripts/automem_watch.py
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS
📚 Learning: 2026-01-06T09:59:50.289Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T09:59:50.289Z
Learning: Applies to **/*.py : Python files must use type hints

Applied to files:

  • scripts/automem_watch.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • scripts/automem_watch.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • scripts/automem_watch.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/models/**/*.py : Use UUID for memory IDs and store in both graph (FalkorDB) and vector (Qdrant) databases for cross-referencing

Applied to files:

  • scripts/automem_watch.py
🧬 Code graph analysis (1)
scripts/automem_watch.py (2)
tests/test_enrichment.py (1)
  • query (27-71)
automem/api/stream.py (1)
  • stream (87-102)
🪛 Ruff (0.14.10)
scripts/automem_watch.py

379-383: Abstract raise to an inner function

(TRY301)


379-383: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (8)
scripts/automem_watch.py (8)

25-28: LGTM - Clean module setup with appropriate type alias.

The Event = Mapping[str, Any] type alias is well-chosen for read-only event payloads, and the module-level Console instance follows the Rich library's recommended pattern.


31-54: LGTM - Defensive helper functions with proper type hints.

The safe conversion functions handle edge cases correctly. Using float(val) before int() in _safe_int properly handles string inputs like "1.5".


57-110: LGTM - Robust event formatting with defensive conversions.

Good use of the safe conversion helpers and appropriate truncation of metadata values. The nested safe_val function is acceptable for its localized use.


179-271: LGTM - Comprehensive enrichment event handling.

The function handles all enrichment event subtypes with appropriate defensive parsing. While lengthy, the logical organization by event type (enrichment.start, enrichment.complete, enrichment.failed) maintains readability. Based on learnings, this aligns with the enrichment pipeline's entity extraction results and metadata structure.


331-348: LGTM - Clean event routing with appropriate fallback.

The process_event dispatcher correctly routes events and uses startswith("enrichment.") to handle all enrichment subtypes through a single handler.


396-418: LGTM - Correct SSE multi-line buffering per RFC 8895.

The implementation properly accumulates data: lines and dispatches on empty line delimiter, with appropriate handling of keepalive comments.


378-383: Intentional raise pattern is acceptable here.

Static analysis flags TRY301/TRY003 for raising HTTPStatusError within the try block, but this is intentional to leverage the unified retry logic in the except handler for 5xx errors. The pattern is pragmatic for this use case.


451-472: LGTM - Clean CLI entry point.

Good practice stripping the trailing slash from the URL to avoid double-slash issues in endpoint construction.

Browser-based alternative to automem_watch.py:
- Visit /monitor to open real-time event stream dashboard
- Token stored in localStorage after first prompt (or via ?token=X)
- Clickable expandable details for all event types
- Color-coded event types (store/recall/enrichment/consolidation)
- Raw JSON toggle for debugging
- Auto-reconnect handled by browser's EventSource
- No SSL issues (browser TLS stack)
- Mobile-friendly responsive design

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 18:56 Inactive
- Replace prompt() with proper login form UI
- Token stored in localStorage (key: automem_token)
- Auto-connect on page load if token exists
- Show error on invalid token / connection failure
- Add disconnect button to clear token and return to login
- /monitor endpoint whitelisted (serves just login form, no data)
- Shared auth with future /graph endpoint (same localStorage key)

Design matches graph-viewer-production.up.railway.app for consistency.
Login to either module authenticates both.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 19:27 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @static/monitor.html:
- Around line 495-714: The HTML is built with raw event fields causing XSS; add
a small escapeHtml(text) helper (using an element's textContent) and replace all
direct insertions of event data in getSummary, renderDetails and the innerHTML
in addEvent with escaped values (e.g., escapeHtml(d.content || ''),
escapeHtml(d.query || ''), escapeHtml(String(d.memory_id || '?'))), and ensure
any mapped/joined fragments (result_summaries, entities, semantic_neighbors,
temporal_links, tags) are escaped before joining so nothing from event.data or
event.type can inject HTML/JS.
- Around line 377-476: Monitor frontend passes the token as ?token= in
tryConnect (EventSource URL) but the backend function _extract_api_token() only
looks for api_key, so authentication fails; update the frontend's tryConnect to
send the token as ?api_key=${token} (replace the token query param) so it
matches _extract_api_token(), and ensure disconnect() and savedToken handling
still reference the same storage key (automem_token) and currentToken;
alternatively, if you prefer backend change, modify _extract_api_token() to also
check for the "token" query parameter in addition to "api_key".
🧹 Nitpick comments (1)
app.py (1)

2738-2750: Enhanced event payload for better observability.

The expanded memory.store event now includes full content, all tags, and complete metadata without truncation. This provides excellent visibility for real-time monitoring.

Note: For memories with very large content, this could result in substantial SSE bandwidth usage. Consider monitoring the impact in production and potentially adding a content length limit for SSE events if needed.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 824a774 and ab08c1b.

📒 Files selected for processing (2)
  • app.py
  • static/monitor.html
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • app.py
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Support authentication via Bearer token, X-API-Key header, or query parameter for API endpoints

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • app.py
🧬 Code graph analysis (1)
app.py (3)
automem/api/stream.py (1)
  • emit_event (22-44)
automem/utils/time.py (1)
  • utc_now (7-9)
automem/utils/tags.py (1)
  • _normalize_tag_list (7-20)
🪛 Ruff (0.14.10)
app.py

3046-3046: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (11)
app.py (8)

113-113: LGTM! Static folder configuration added.

The addition of static_folder="static" enables serving static assets, which is necessary for the new /monitor endpoint introduced later in this file.


145-147: Verify authentication requirement for the monitor page.

The /monitor endpoint is whitelisted for unauthenticated access. While the actual SSE stream at /stream requires a token, the monitor HTML page is publicly accessible. This means anyone can reach the login form without authentication.

This may be intentional for user convenience, but ensure this aligns with your security requirements. If the monitor should be restricted, consider requiring authentication for the /monitor endpoint as well.


1768-1796: LGTM! Enrichment worker correctly handles new dict return type.

The worker properly consumes the enrichment result dictionary and emits comprehensive event data for real-time monitoring. The breaking change from bool to Dict[str, Any] is well-integrated here.


2099-2259: LGTM! Breaking change properly implemented.

The return type change from bool to Dict[str, Any] is consistent across all code paths and provides rich enrichment metadata. The comprehensive return structure includes:

  • Processed status and reason
  • Original content and tags
  • Entity extraction results
  • Relationship links (temporal, semantic, patterns)
  • Generated summary

All enrichment worker call sites have been updated to handle the new return type.


2262-2302: LGTM! Enhanced temporal relationship tracking.

The change from returning an int count to List[str] of linked memory IDs provides more useful information for monitoring and enrichment metadata. The implementation correctly collects and returns all linked IDs.


3023-3079: LGTM! Comprehensive recall event analytics.

The expanded memory.recall event provides excellent observability with:

  • Full query text and result counts
  • Aggregate statistics (avg_length, avg_tags, score_range)
  • Top 3 result summaries with type, score, and dimensions
  • Deduplication metrics

The try/except block properly handles response parsing failures without breaking the recall flow.


3159-3169: LGTM! Association event tracking added.

The memory.associate event emission provides visibility into relationship creation between memories, including IDs, relation type, and strength.


3826-3829: LGTM! Monitor route serves static dashboard.

The /monitor endpoint correctly serves the browser-based SSE monitoring dashboard using Flask's send_static_file() method.

static/monitor.html (3)

1-6: LGTM! Standard HTML5 structure with proper viewport configuration.


444-476: LGTM! Robust connection and error handling.

The connection logic properly distinguishes between initial connection failures (likely auth issues) and reconnection scenarios. The hasConnected flag ensures invalid tokens don't get saved to localStorage, and reconnection attempts are tracked for user visibility.


7-309: LGTM! Well-structured CSS with modern design patterns.

The styling uses CSS custom properties for theming, implements a professional dark theme, and provides a responsive layout. The event type color coding enhances usability for real-time monitoring.

Server's _extract_api_token() only accepts api_key query param,
not token. This fixes 401 errors when connecting to /stream.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 19:41 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @static/monitor.html:
- Around line 490-493: renderTags and other render functions insert untrusted
fields (content, query, error, summary, tags, entity names) directly into
innerHTML causing XSS risk; add a single helper like escapeHtml(unsafe) that
safely replaces &, <, >, ", ' or replace innerHTML usage by creating elements
and setting textContent for all dynamic values (apply to renderTags,
renderDetails, getSummary and any other places noted between lines ~517-714) and
ensure every interpolation of tags, content, summary, query, error, and entity
names is passed through escapeHtml or assigned via textContent instead of
innerHTML.
- Line 439: The EventSource instantiation uses the raw token when building the
URL (eventSource = new EventSource(`${baseUrl}/stream?api_key=${token}`)), which
can break if the token contains special characters; update the URL construction
to URL-encode the token (use encodeURIComponent(token)) before concatenation so
the api_key query value is properly escaped and the EventSource connects
reliably.
🧹 Nitpick comments (3)
static/monitor.html (3)

699-699: Consider using addEventListener instead of inline onclick.

The inline onclick attribute works but is less maintainable than attaching event listeners via JavaScript. This would also avoid mixing JavaScript in HTML strings.

♻️ Alternative approach
             div.innerHTML = `
-                <div class="event-header" onclick="this.parentElement.classList.toggle('open')">
+                <div class="event-header">
                     <span class="event-type type-${typeClass}">${event.type.split('.').pop()}</span>
                     <span class="event-summary">${getSummary(event)}</span>
                     <span class="event-time">${formatTime(event.timestamp)}</span>
                     <span class="event-toggle">▶</span>
                 </div>
                 <div class="event-details">${renderDetails(event)}</div>
             `;
+            
+            div.querySelector('.event-header').addEventListener('click', function() {
+                this.parentElement.classList.toggle('open');
+            });

462-475: Consider handling token expiration during reconnection.

The error handler correctly distinguishes between initial connection failure (likely auth) and subsequent disconnections (network issues). However, if the token expires while the monitor is running, the EventSource will continue attempting to reconnect indefinitely, showing "Reconnecting..." without prompting for a new token.

Consider adding a reconnection attempt limit or a timeout that returns to the login screen if reconnection fails repeatedly.

💡 Suggested enhancement
         let eventSource = null;
         let eventCount = 0;
         let reconnectCount = 0;
         let currentToken = null;
+        let reconnectAttempts = 0;
+        const MAX_RECONNECT_ATTEMPTS = 10;

         // ... 

         eventSource.onerror = () => {
             if (!hasConnected) {
                 // Never connected - likely auth failure
                 eventSource.close();
                 eventSource = null;
                 showLoginScreen('Invalid token or connection failed');
             } else {
                 // Was connected, now reconnecting
                 statusEl.textContent = 'Reconnecting...';
                 statusEl.className = 'disconnected';
                 reconnectCount++;
                 reconnectCountEl.textContent = reconnectCount;
+                reconnectAttempts++;
+                
+                // If too many reconnect attempts, assume token expired
+                if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
+                    eventSource.close();
+                    eventSource = null;
+                    disconnect();
+                    showLoginScreen('Connection lost. Please reconnect.');
+                }
             }
         };
         
         eventSource.onopen = () => {
             hasConnected = true;
+            reconnectAttempts = 0;  // Reset on successful connection
             localStorage.setItem('automem_token', token);
             // ...
         };

453-460: Consider displaying JSON parse errors in the UI.

Parse failures are currently only logged to the console. For better observability, consider showing these errors in the monitor UI so users are aware when events fail to display.

💡 Suggested enhancement
         eventSource.onmessage = (e) => {
             try {
                 const event = JSON.parse(e.data);
                 addEvent(event);
             } catch (err) {
                 console.error('Failed to parse event:', e.data);
+                // Show parse error in UI
+                addEvent({
+                    type: 'error.parse',
+                    timestamp: new Date().toISOString(),
+                    data: { error: 'Failed to parse event data', raw: e.data.slice(0, 100) }
+                });
             }
         };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab08c1b and e766140.

📒 Files selected for processing (1)
  • static/monitor.html
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS

…ine data

- Add status header cards (Health, Memories, Enrichment, Consolidation)
- Show expanded event cards with inline tags, entities, links with scores
- Add performance badges (green/yellow/red) based on configurable thresholds
- Add filter controls (All/Store/Recall/Enrich/Consolidation/Errors)
- Add rolling stats (event count, error count, avg latency)
- Auto-refresh status endpoints every 30 seconds
- Quality badges for warnings (large content, low confidence, no tags)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 10, 2026 20:43 Inactive
- Add flask-cors 4.0.0 dependency
- Enable CORS globally for Graph Viewer and other cross-origin clients

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 11, 2026 01:05 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @app.py:
- Around line 1147-1150: The unauthenticated-exempt check in require_api_token
currently only allows health and /monitor but blocks Flask static assets,
breaking the monitor page when API_TOKEN is set; update the condition that
computes endpoint = request.endpoint or "" in app.py so it also returns early
for static assets (e.g., endpoint == "static" or
request.path.startswith("/static/") ) in addition to the existing checks for
health and /monitor.
- Around line 3025-3081: The parsing code can raise when mem.get("tags") is None
(making len(...) fail) and the broad except Exception hides specific errors;
update the loop that builds result_summaries to coerce tags safely (e.g., tags =
mem.get("tags") or []; tags_count = len(tags) if isinstance(tags, (list, tuple))
else 0) and use that tags_count in the dict, and replace the broad except
Exception with a narrower catch (e.g., except (TypeError, AttributeError,
ValueError, KeyError) as e) so only expected parse errors are caught while other
exceptions surface.
- Around line 1770-1797: The code currently calls
state.enrichment_stats.record_success(job.memory_id) unconditionally after
enrich_memory() returns, which counts skipped enrichments as successes; update
the logic to record successes only when result.get("processed") is truthy and
record skips otherwise by adding or calling a record_skip(job.memory_id) on the
EnrichmentStats class (or adjust EnrichmentStats to accept a status flag), i.e.,
move or gate the record_success call to execute only when
result.get("processed") is true and call record_skip when processed is false
(use the existing enrich_memory, result.get("processed"),
state.enrichment_stats.record_success, and a new
state.enrichment_stats.record_skip to locate where to change the code).

In @requirements.txt:
- Line 3: Update the vulnerable dependency in requirements.txt by bumping the
flask-cors spec to a secure minimum (e.g., change "flask-cors==4.0.0" to
"flask-cors>=6.0.0"), then regenerate your lockfile/virtual environment
(pip-compile, pip freeze, or reinstall deps) and run your test suite and
security scanner to confirm the GHSA issues are resolved; ensure any CI or
deployment manifests referencing flask-cors are updated to the new constraint as
well.
🧹 Nitpick comments (2)
app.py (2)

2101-2261: enrich_memory() schema: consider normalizing tags (case) and avoid truncating IDs in the returned semantic_neighbors.

  • Tags are taken from stored properties without lowercasing; elsewhere tags/prefix filtering often assumes normalized casing.
  • Return value truncates neighbor IDs (nid[:8]) while metadata["enrichment"]["semantic_neighbors"] stores full IDs—this can confuse consumers/debugging.
    Based on learnings, enrichment metadata + entity tags + enriched_at look aligned.
Proposed patch (safer tags_count + full IDs in return)
-    original_tags = list(dict.fromkeys(_normalize_tag_list(properties.get("tags"))))
+    original_tags = [
+        t.strip().lower()
+        for t in dict.fromkeys(_normalize_tag_list(properties.get("tags")))
+        if isinstance(t, str) and t.strip()
+    ]
     tags = list(original_tags)  # Copy for modification
@@
     return {
         "processed": True,
         "content": content,
         "tags_before": original_tags,
         "tags_after": tags,
         "tags_added": tags_added,
         "entities": entities,
         "temporal_links": temporal_link_ids,
-        "semantic_neighbors": [(nid[:8], round(score, 3)) for nid, score in semantic_neighbors],
+        "semantic_neighbors": [(nid, round(score, 3)) for nid, score in semantic_neighbors],
         "patterns_detected": pattern_info,
         "summary": (summary or ""),
     }

31-32: Remove global CORS(app) or restrict it to specific routes and origins with an allowlist.
Currently CORS is enabled for all routes without any origin restriction. While the monitor page and static assets use embedded CSS (no external asset loading), global CORS without an allowlist is a security hardening gap—especially if this API is exposed publicly or if you plan to add endpoints that handle sensitive cross-origin requests. Best practice is to either disable CORS entirely or configure it with explicit route-level and origin-level scoping via environment variables.

Suggested approach
 app = Flask(__name__, static_folder="static")
-CORS(app)  # Enable CORS for Graph Viewer and other cross-origin clients
+allowed_origins = [
+    o.strip()
+    for o in (os.getenv("CORS_ALLOWED_ORIGINS", "") or "").split(",")
+    if o.strip()
+]
+if allowed_origins:
+    CORS(
+        app,
+        resources={r"/graph/*": {"origins": allowed_origins}},
+        supports_credentials=False,
+    )

Also applies to: 114-116

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78d002c and 6d5d8be.

📒 Files selected for processing (2)
  • app.py
  • requirements.txt
🧰 Additional context used
📓 Path-based instructions (2)
{requirements*.txt,setup.py,pyproject.toml,Pipfile}

📄 CodeRabbit inference engine (CLAUDE.md)

Pin onnxruntime<1.20 when using fastembed 0.4.x to avoid compatibility issues

Files:

  • requirements.txt
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • app.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Establish temporal (PRECEDED_BY) and semantic (SIMILAR_TO) edges with symmetric cosine scores from Qdrant in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Support authentication via Bearer token, X-API-Key header, or query parameter for API endpoints

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Implement enrichment pipeline as queue-backed worker that consumes EnrichmentJob objects created on each memory write

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline

Applied to files:

  • app.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • app.py
🧬 Code graph analysis (1)
app.py (3)
automem/api/stream.py (1)
  • emit_event (22-44)
automem/utils/time.py (1)
  • utc_now (7-9)
automem/utils/tags.py (1)
  • _normalize_tag_list (7-20)
🪛 OSV Scanner (2.3.1)
requirements.txt

[HIGH] 3-3: flask-cors 4.0.0: undefined

(PYSEC-2024-71)


[HIGH] 3-3: flask-cors 4.0.0: Flask-CORS vulnerable to Improper Handling of Case Sensitivity

(GHSA-43qf-4rqw-9q2g)


[HIGH] 3-3: flask-cors 4.0.0: Flask-CORS improper regex path matching vulnerability

(GHSA-7rxf-gvfg-47g4)


[HIGH] 3-3: flask-cors 4.0.0: flask-cors vulnerable to log injection when the log level is set to debug

(GHSA-84pr-m4jr-85g5)


[HIGH] 3-3: flask-cors 4.0.0: Flask-CORS allows for inconsistent CORS matching

(GHSA-8vgw-p6qm-5gr7)


[HIGH] 3-3: flask-cors 4.0.0: Flask-CORS allows the Access-Control-Allow-Private-Network CORS header to be set to true by default

(GHSA-hxwh-jpp2-84pm)

🪛 Ruff (0.14.10)
app.py

3048-3048: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (4)
app.py (4)

2264-2305: find_temporal_relationships() return change looks good; consider batching relationship creation if this ever grows beyond limit=5.
Current N+1 is fine at limit=5.


2736-2752: memory.store SSE payload expansion LGTM; be aware this can emit full content/metadata (potential PII) to all subscribers.
Just ensure SSE access control matches your expectations.


3161-3171: memory.associate SSE event: LGTM.
Good minimal payload for observability.


3828-3832: /monitor static route LGTM; confirm it’s intended to be unauthenticated in token-protected deployments.
This pairs with the require_api_token() bypass and CORS changes.

- Add AUTOMEM_EVENT_LOG env var to enable file-based event logging
- Add AUTOMEM_EVENT_LOG_MAX to limit log size (default: 500 events)
- Events appended to JSONL file, auto-truncated when limit exceeded
- New /stream/history endpoint returns cached events for UI hydration
- New /stream/log-status endpoint shows log enabled state and size
- Monitor UI fetches history on connect and displays log status
- Historical events render without animation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 11, 2026 21:32 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
automem/api/stream.py (1)

100-153: Improve error handling specificity.

Both functions use bare Exception catches that silently fail:

  • Lines 122-123 in get_event_history
  • Lines 143-144 in get_log_status

This makes debugging difficult when file operations fail. Consider:

  1. Catching specific exceptions: (IOError, OSError, json.JSONDecodeError)
  2. Logging errors for observability
  3. The try-except-pass pattern in get_log_status (lines 143-144) completely hides failures
♻️ Proposed improvements
 def get_event_history(limit: int = 100) -> List[Dict[str, Any]]:
     """Return recent events from the log file.
 
     Args:
         limit: Maximum number of events to return
 
     Returns:
         List of event dictionaries, oldest first
     """
     if not _event_log_path:
         return []
 
     path = Path(_event_log_path)
     if not path.exists():
         return []
 
     with _event_log_lock:
         try:
             with open(path, "r") as f:
                 lines = [line.strip() for line in f if line.strip()]
             # Return last N events
             return [json.loads(line) for line in lines[-limit:]]
-        except Exception:
+        except (IOError, OSError, json.JSONDecodeError) as e:
+            import logging
+            logging.warning(f"Failed to read event history: {e}")
             return []


 def get_log_status() -> Dict[str, Any]:
     """Return event log status for display.
 
     Returns:
         Dict with enabled, path, size_bytes, event_count, max_events
     """
     enabled = bool(_event_log_path)
     size = 0
     count = 0
 
     if enabled:
         path = Path(_event_log_path)
         if path.exists():
             try:
                 size = path.stat().st_size
                 with open(path, "r") as f:
                     count = sum(1 for line in f if line.strip())
-            except Exception:
-                pass
+            except (IOError, OSError) as e:
+                import logging
+                logging.warning(f"Failed to read log status: {e}")
 
     return {
         "enabled": enabled,
         "path": _event_log_path or None,
         "size_bytes": size,
         "event_count": count,
         "max_events": _event_log_max,
     }
static/monitor.html (1)

414-498: Consider moving inline event handler for CSP compliance.

The HTML structure is clean and semantic. However, there's an inline onclick handler that will be added to event cards (line 1005 in the JavaScript section). For better Content Security Policy (CSP) compliance and separation of concerns, consider using event delegation in the JavaScript instead.

This is a minor point and the current implementation works fine. It's more about future-proofing if you decide to implement stricter CSP headers.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d5d8be and 8eabae6.

📒 Files selected for processing (2)
  • automem/api/stream.py
  • static/monitor.html
🧰 Additional context used
📓 Path-based instructions (3)
automem/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

automem/**/*.py: Ensure graph writes always succeed even if vector storage fails by implementing graceful degradation in embedding and vector store operations
Normalize all timestamps to UTC ISO format for storage and retrieval
Implement graph operations as atomic transactions with automatic rollback on errors
Log vector store errors but do not block graph writes when vector storage fails

Files:

  • automem/api/stream.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • automem/api/stream.py
automem/api/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Flask blueprints should be organized in the automem/api/ directory

Files:

  • automem/api/stream.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics
🧬 Code graph analysis (1)
automem/api/stream.py (2)
automem/utils/time.py (1)
  • utc_now (7-9)
app.py (1)
  • require_api_token (1143-1154)
🪛 Ruff (0.14.10)
automem/api/stream.py

49-49: Do not catch blind exception: Exception

(BLE001)


122-122: Do not catch blind exception: Exception

(BLE001)


143-144: try-except-pass detected, consider logging the exception

(S110)


143-143: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (9)
automem/api/stream.py (3)

1-28: LGTM: Clean configuration setup.

The event log configuration with dedicated lock for thread-safe file access is well-structured.


64-92: Good integration of logging into emit flow.

The placement of _write_event_to_log before SSE emission ensures events are persisted even if all subscribers are slow/dropped. This aligns with graceful degradation principles.


217-234: LGTM: Well-secured endpoints with proper safeguards.

Both endpoints correctly enforce authentication and include sensible limits (capping at 500 events). The response format is consistent with Flask best practices.

static/monitor.html (6)

7-412: LGTM: Well-organized styling.

The CSS is cleanly structured with custom properties for theming, responsive layouts, and appropriate animations. Dark theme implementation looks solid.


500-533: LGTM: Clean state management and auto-reconnect.

The state initialization and auto-connect pattern with localStorage provides good UX. Token storage in localStorage is appropriate for this dashboard use case.


594-638: Token in URL query parameter - EventSource API limitation.

Line 601 passes the API token as a query parameter in the EventSource URL:

eventSource = new EventSource(`${baseUrl}/stream?api_key=${token}`);

Security consideration: Query parameters are typically logged in server logs, proxy logs, and browser history. The EventSource API doesn't support custom headers (unlike fetch/XMLHttpRequest), so this is a known limitation.

Mitigation options:

  1. Accept this limitation (query param is already authenticated on server)
  2. Use a short-lived session token instead of the main API token
  3. Switch to fetch + ReadableStream for SSE if custom headers are required

For an internal dashboard with authenticated users, the current approach is acceptable but worth documenting.

Based on learnings and coding guidelines, consider if this aligns with security requirements. If strict token protection is needed, evaluate alternatives to EventSource.


640-737: LGTM: Robust status fetching with proper error handling.

All fetch operations include error handling, use parallel requests where appropriate, and gracefully degrade on failure. The historical event hydration on connect provides good UX.


960-1021: Good event management with memory limits.

The event limiting (200 events in memory and DOM), historical event handling without animation, and automatic filter application provide solid UX and performance characteristics.

Note: XSS concerns with innerHTML in this function are covered in the previous comment on the rendering functions.


1023-1029: LGTM: Clean filter implementation.

Simple and effective filtering using CSS classes. Performance is good given the 200-event limit.

- Document AUTOMEM_EVENT_LOG and AUTOMEM_EVENT_LOG_MAX env vars
- Add Streaming (SSE) section with /stream/* endpoints
- Add Web UI section with /monitor endpoint

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to automem / production January 11, 2026 22:59 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CLAUDE.md (1)

45-45: Update the endpoint count to reflect new additions.

The documentation states "13 endpoints" but with the addition of 5 new endpoints in the Streaming (SSE) and Web UI sections (lines 70-78), the total should now be 18.

📝 Suggested fix
-The API (`app.py`) provides 13 endpoints:
+The API (`app.py`) provides 18 endpoints:
🧹 Nitpick comments (1)
CLAUDE.md (1)

70-74: Consider clarifying authentication requirements for all streaming endpoints.

Only /stream explicitly mentions "(requires auth)" while /stream/status, /stream/history, and /stream/log-status don't specify their authentication requirements. For consistency and developer clarity, consider documenting whether these endpoints are authenticated or public.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8eabae6 and 37ad479.

📒 Files selected for processing (1)
  • CLAUDE.md
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Configure enrichment pipeline with environment variables: ENRICHMENT_MAX_ATTEMPTS (default 3), ENRICHMENT_SIMILARITY_LIMIT (default 5), ENRICHMENT_SIMILARITY_THRESHOLD (default 0.8), ENRICHMENT_IDLE_SLEEP_SECONDS (default 2), ENRICHMENT_FAILURE_BACKOFF_SECONDS (default 5), ENRICHMENT_ENABLE_SUMMARIES (default true)

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Extract entities (tools/projects/people/organisations/concepts) using spaCy when available (configured via ENRICHMENT_SPACY_MODEL), otherwise fall back to regex heuristics

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Add enrichment metadata including short summaries, `enriched_at` timestamp, and per-run metrics under `metadata.enrichment`

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Process enrichment pipeline asynchronously with automatic retries using configurable ENRICHMENT_MAX_ATTEMPTS

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Detect recurring patterns per memory type, strengthen shared Pattern nodes, and link memories via EXEMPLIFIES relationships with key terms in enrichment pipeline

Applied to files:

  • CLAUDE.md
🔇 Additional comments (2)
CLAUDE.md (2)

70-78: Well-documented new API surface.

The new Streaming (SSE) and Web UI sections are clearly documented with concise, informative descriptions. The note about "monitor hydration" on line 73 effectively explains the purpose of the history endpoint.


243-245: Clear and complete environment variable documentation.

The new event logging configuration variables are well-documented with clear purposes, sensible defaults, and proper formatting consistent with the rest of the configuration section.

Deleted .github/workflows/auto-fix.yml and .github/codex/prompts/fix-ci.md to clean up unused CI automation and prompt configuration files.
@railway-app railway-app bot temporarily deployed to automem / production January 13, 2026 13:18 Inactive
@railway-app railway-app bot temporarily deployed to automem / production January 13, 2026 19:41 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
automem/api/recall.py (2)

11-14: Minor inconsistency in dependency injection pattern.

utc_now is imported directly here, while automem/api/memory.py receives it via the blueprint factory parameter. Both work correctly, but the inconsistent DI pattern may affect testability of recall_memories (harder to mock timestamp).

♻️ Consider accepting utc_now as a parameter for consistency
 def create_recall_blueprint(
     get_memory_graph: Callable[[], Any],
     get_qdrant_client: Callable[[], Any],
     normalize_tag_list: Callable[[Any], List[str]],
     normalize_timestamp: Callable[[str], str],
     parse_time_expression: Callable[[Optional[str]], Tuple[Optional[str], Optional[str]]],
     extract_keywords: Callable[[str], List[str]],
     compute_metadata_score: Callable[
         [Dict[str, Any], str, List[str], Optional[Dict[str, Any]]], tuple[float, Dict[str, float]]
     ],
     result_passes_filters: Callable[
         [Dict[str, Any], Optional[str], Optional[str], Optional[List[str]], str, str], bool
     ],
     graph_keyword_search: Callable[..., List[Dict[str, Any]]],
     vector_search: Callable[..., List[Dict[str, Any]]],
     vector_filter_only_tag_search: Callable[..., List[Dict[str, Any]]],
     recall_max_limit: int,
     logger: Any,
     allowed_relations: List[str] | set[str] | tuple[str, ...] | Any = (),
     relation_limit: int = 5,
     serialize_node: Callable[[Any], Dict[str, Any]] | None = None,
     summarize_relation_node: Callable[[Dict[str, Any]], Dict[str, Any]] | None = None,
     on_access: Optional[Callable[[List[str]], None]] = None,
+    utc_now: Callable[[], str] = utc_now,
 ) -> Blueprint:

1482-1545: Implementation is sound; consider round() for elapsed_ms consistency.

The stats calculation is well-protected against division by zero (line 1536 check). However, elapsed_ms is cast with int() (truncation) while the existing query_time_ms in the response uses round(). This creates a minor inconsistency.

♻️ Use round() for consistency with existing timing fields
-        elapsed_ms = int((time.perf_counter() - query_start) * 1000)
+        elapsed_ms = round((time.perf_counter() - query_start) * 1000, 2)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 151305e and 0615de7.

📒 Files selected for processing (2)
  • automem/api/memory.py
  • automem/api/recall.py
🧰 Additional context used
📓 Path-based instructions (3)
automem/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

automem/**/*.py: Ensure graph writes always succeed even if vector storage fails by implementing graceful degradation in embedding and vector store operations
Normalize all timestamps to UTC ISO format for storage and retrieval
Implement graph operations as atomic transactions with automatic rollback on errors
Log vector store errors but do not block graph writes when vector storage fails

Files:

  • automem/api/recall.py
  • automem/api/memory.py
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Run 'black .' to format Python code before committing
Run 'flake8' to lint Python code for quality issues

**/*.py: Python files must use type hints
Indent Python code with 4 spaces; maintain line length of 100 characters (enforced by Black)
Use snake_case for module and function names
Use PascalCase for class names
Use UPPER_SNAKE_CASE for constants
Use Black for code formatting
Use Isort with profile=black for import sorting
Use Flake8 for linting Python code

Files:

  • automem/api/recall.py
  • automem/api/memory.py
automem/api/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Flask blueprints should be organized in the automem/api/ directory

Files:

  • automem/api/recall.py
  • automem/api/memory.py
🧠 Learnings (3)
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/app.py : Implement recall scoring that combines vector similarity, keyword match, tag overlap, and recency

Applied to files:

  • automem/api/recall.py
📚 Learning: 2026-01-06T09:59:50.289Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T09:59:50.289Z
Learning: Applies to automem/api/*.py : Flask blueprints should be organized in the `automem/api/` directory

Applied to files:

  • automem/api/recall.py
  • automem/api/memory.py
📚 Learning: 2025-12-09T02:15:22.291Z
Learnt from: CR
Repo: verygoodplugins/automem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-09T02:15:22.291Z
Learning: Applies to automem/**/*enrichment*.py : Write entity extraction results to memory metadata with entity tags in format `entity:<type>:<slug>`

Applied to files:

  • automem/api/memory.py
🧬 Code graph analysis (2)
automem/api/recall.py (2)
automem/api/stream.py (2)
  • stream (214-229)
  • emit_event (89-116)
automem/utils/time.py (1)
  • utc_now (7-9)
automem/api/memory.py (2)
automem/api/stream.py (2)
  • stream (214-229)
  • emit_event (89-116)
automem/utils/time.py (1)
  • utc_now (7-9)
🔇 Additional comments (5)
automem/api/memory.py (3)

10-10: LGTM!

Import correctly brings in emit_event for SSE telemetry.


350-366: LGTM!

Event emission correctly follows the emit_event signature, includes all required STORE payload fields per PR objectives (full content, tags, metadata, embedding/Qdrant status), and uses the injected utc_now callable.


632-643: LGTM!

Association event correctly emits relationship details including both memory IDs, relation type, strength, and full properties dict.

automem/api/recall.py (2)

1462-1481: LGTM!

The wrapper timing captures full endpoint latency (including Flask response serialization), which complements the internal handle_recall timing. This provides accurate telemetry for SSE consumers.


1547-1567: LGTM!

Event payload correctly implements PR requirements: full query text, aggregate stats (avg_length, avg_tags, score_range), top 3 result summaries with type/score/length/tags, and dedup-removed count.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant