-
Notifications
You must be signed in to change notification settings - Fork 76
feat(watch): Enhanced SSE monitoring with detailed event data #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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>
📝 WalkthroughSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
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>
There was a problem hiding this 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=Noneis appropriate for long-lived SSE connections (the static analysis hint is a false positive in this context), consider usinghttpx.Timeoutwith 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 Exceptionon 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
📒 Files selected for processing (2)
app.pyscripts/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.pyscripts/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_memoryreturn 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
booltoDict[str, Any]is documented in the PR objectives. All code paths return a consistent dict structure withprocessedand eitherreason(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
contentandmetadatafields 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_previewin 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 Exceptionon 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.associateSSE 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 fromapp.py.
193-247: LGTM!Clean implementations for consolidation, error, associate, and raw event printers. The
print_raw_eventfallback 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>
There was a problem hiding this 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_memoryreturn type. However, the test only validatesresult.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
📒 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 variablePYTEST_DISABLE_PLUGIN_AUTOLOAD=1when 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 thetests/directory with filenames matchingtest_*.py
Use Pytest as the testing framework with fixtures preferred over global variables
Integration tests should be placed intests/and run withmake test-integrationusing 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)
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>
There was a problem hiding this 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 whenresult["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 inenrich_memory()return payload (truncate only in display).
semantic_neighborsis returned as[(nid[:8], ...)], which limits downstream consumers and conflicts with the idea of “detailed event data”. Sincescripts/automem_watch.pyalready 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; preferDISTINCT(or dedup in Python).
RETURN m2.idwithoutDISTINCTcan produce duplicatelinked_idsif 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 broadExceptioncatch; also aggregate stats currently computed from top-10 summaries only.
- The
try/except Exceptionaround 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_rangeare computed fromresult_summaries(top 10), not all results; if the intent is “aggregate stats”, compute from fullresultswhen 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: SSEmemory.storenow broadcasts full content + metadata without filtering: data exfiltration risk.The
/streamendpoint uses identical token auth as regular API endpoints—no separate restriction. Sinceemit_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 newenrich_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’sDummyGraphfixture if that’s the standard for FalkorDB-related tests.Also applies to: 122-122
scripts/automem_watch.py (1)
27-31:format_timestamp()should acceptOptional[str]and parse ISO more robustly.Right now
None(or non-ISO strings) can throw or format oddly. Consider usingdatetime.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
📒 Files selected for processing (3)
app.pyscripts/automem_watch.pytests/test_enrichment.py
🧰 Additional context used
📓 Path-based instructions (3)
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Set environment variablePYTEST_DISABLE_PLUGIN_AUTOLOAD=1when 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.pyscripts/automem_watch.pyapp.py
tests/test_*.py
📄 CodeRabbit inference engine (AGENTS.md)
tests/test_*.py: Place tests in thetests/directory with filenames matchingtest_*.py
Use Pytest as the testing framework with fixtures preferred over global variables
Integration tests should be placed intests/and run withmake test-integrationusing 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.pyapp.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.pyapp.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.pyapp.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.pyapp.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.pyapp.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: SSEmemory.associatepayload 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 ofsemantic_neighborson line 200 is correct: the enrichment event streamssemantic_neighborsas a list of tuples(nid, score)(fromenrich_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>
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>
There was a problem hiding this 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, notmax_output_tokens. Themax_output_tokensparameter is for the Responses API, not Chat Completions.- gpt-4/gpt-4o-mini (line 157): Should use
max_completion_tokens, not the deprecatedmax_tokens. Themax_tokensparameter has been replaced bymax_completion_tokensin 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_tokensfor 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 dtThis is a stylistic preference and the current code is clear and correct.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app.pyautomem/utils/text.pyautomem/utils/time.pytests/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.pyautomem/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.pyapp.pytests/test_app.pyautomem/utils/time.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Set environment variablePYTEST_DISABLE_PLUGIN_AUTOLOAD=1when 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 thetests/directory with filenames matchingtest_*.py
Use Pytest as the testing framework with fixtures preferred over global variables
Integration tests should be placed intests/and run withmake test-integrationusing 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.pyautomem/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_accessedhandles 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: Falseand 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.
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>
There was a problem hiding this 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
📒 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>
There was a problem hiding this 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
📒 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_intcorrectly 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_eventandprint_error_eventare 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=strinjson.dumpsis a good defensive practice.
338-381: LGTM - Broad exception handling is appropriate here.The broad
Exceptioncatch 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 returnssemantic_neighborsas 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 aValueError.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>
There was a problem hiding this 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 thestrengthfield 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
📒 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
Consoleinstance 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_filterand 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
Syntaxfor 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>
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>
There was a problem hiding this 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:
- No change validation: Changes are committed without verifying they actually fix the issue or pass tests
- No human review: Direct push bypasses PR review, even for potentially breaking changes
- [skip ci] prevents validation: Changes aren't tested before merge
- No rollback mechanism: If auto-fix introduces new failures, there's no automated recovery
Consider these improvements:
- Run tests after Codex makes changes but before committing
- Create a PR instead of direct push for non-trivial changes
- Remove
[skip ci]or use a separate validation workflow- Add a comment to the original PR linking to the auto-fix commit
- 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
📒 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: 0is appropriate for ensuring Codex has access to full repository history if needed.
51-76: The--log-failedflag is valid and correctly used.The GitHub CLI documentation confirms that
gh run view --log-failedis a valid option that displays logs only for failed steps in a workflow run. The PR comment filtering syntax usinggh pr view --json comments -q '.comments[] | select(.author.login == "coderabbitai")'also follows documented patterns correctly.The code as written is functionally sound. The
tail -200limit 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>
There was a problem hiding this 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
📒 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-levelConsoleinstance 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)beforeint()in_safe_intproperly 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_valfunction 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_eventdispatcher correctly routes events and usesstartswith("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
HTTPStatusErrorwithin 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>
- 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>
There was a problem hiding this 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.storeevent 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
📒 Files selected for processing (2)
app.pystatic/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/monitorendpoint introduced later in this file.
145-147: Verify authentication requirement for the monitor page.The
/monitorendpoint is whitelisted for unauthenticated access. While the actual SSE stream at/streamrequires 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
/monitorendpoint 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
booltoDict[str, Any]is well-integrated here.
2099-2259: LGTM! Breaking change properly implemented.The return type change from
booltoDict[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
intcount toList[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.recallevent 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.associateevent emission provides visibility into relationship creation between memories, including IDs, relation type, and strength.
3826-3829: LGTM! Monitor route serves static dashboard.The
/monitorendpoint correctly serves the browser-based SSE monitoring dashboard using Flask'ssend_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
hasConnectedflag 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>
There was a problem hiding this 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
onclickattribute 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
📒 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>
- 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>
There was a problem hiding this 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 returnedsemantic_neighbors.
- Tags are taken from stored properties without lowercasing; elsewhere tags/prefix filtering often assumes normalized casing.
- Return value truncates neighbor IDs (
nid[:8]) whilemetadata["enrichment"]["semantic_neighbors"]stores full IDs—this can confuse consumers/debugging.
Based on learnings, enrichment metadata + entity tags +enriched_atlook 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 globalCORS(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
📒 Files selected for processing (2)
app.pyrequirements.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
[HIGH] 3-3: flask-cors 4.0.0: Flask-CORS improper regex path matching vulnerability
[HIGH] 3-3: flask-cors 4.0.0: flask-cors vulnerable to log injection when the log level is set to debug
[HIGH] 3-3: flask-cors 4.0.0: Flask-CORS allows for inconsistent CORS matching
[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
🪛 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 beyondlimit=5.
Current N+1 is fine atlimit=5.
2736-2752:memory.storeSSE 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.associateSSE event: LGTM.
Good minimal payload for observability.
3828-3832:/monitorstatic route LGTM; confirm it’s intended to be unauthenticated in token-protected deployments.
This pairs with therequire_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>
There was a problem hiding this 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
Exceptioncatches that silently fail:
- Lines 122-123 in
get_event_history- Lines 143-144 in
get_log_statusThis makes debugging difficult when file operations fail. Consider:
- Catching specific exceptions:
(IOError, OSError, json.JSONDecodeError)- Logging errors for observability
- The
try-except-passpattern inget_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
onclickhandler 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
📒 Files selected for processing (2)
automem/api/stream.pystatic/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_logbefore 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:
- Accept this limitation (query param is already authenticated on server)
- Use a short-lived session token instead of the main API token
- 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
innerHTMLin 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>
There was a problem hiding this 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
/streamexplicitly mentions "(requires auth)" while/stream/status,/stream/history, and/stream/log-statusdon'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
📒 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.
There was a problem hiding this 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_nowis imported directly here, whileautomem/api/memory.pyreceives it via the blueprint factory parameter. Both work correctly, but the inconsistent DI pattern may affect testability ofrecall_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; considerround()for elapsed_ms consistency.The stats calculation is well-protected against division by zero (line 1536 check). However,
elapsed_msis cast withint()(truncation) while the existingquery_time_msin the response usesround(). 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
📒 Files selected for processing (2)
automem/api/memory.pyautomem/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.pyautomem/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.pyautomem/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.pyautomem/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.pyautomem/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_eventfor SSE telemetry.
350-366: LGTM!Event emission correctly follows the
emit_eventsignature, includes all required STORE payload fields per PR objectives (full content, tags, metadata, embedding/Qdrant status), and uses the injectedutc_nowcallable.
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_recalltiming. 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.
Summary
Adds detailed event data to SSE stream for better real-time observability of memory operations.
STORE events now include:
RECALL events now include:
ENRICHMENT events now include:
Client improvements:
Breaking change:
enrich_memory()now returnsDict[str, Any]instead ofboolTest plan
python scripts/automem_watch.py --url http://localhost:8001 --token dev🤖 Generated with Claude Code