Add Node.js agent templates and unlock JavaScript in agent builder#3374
Conversation
Add conversational and recommendation agent templates using node-redis v4 with Redis Search for vector-based message history and movie indexing. Conversational agent uses hybrid recent+semantic context retrieval, runtime embedding dimension validation, and a clear note on Redis version requirements. Recommendation agent parses genres from MovieLens CSV format into Redis TAGs, validates LLM query params against an explicit allowlist, skips dataset reload if the index is already warm, and filters genres in Redis rather than JS. Fix template URL to use root-relative path so local templates load in dev. Update agent builder to support JavaScript alongside Python. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🛡️ Jit Security Scan Results✅ No security findings were detected in this PR
Security scan by Jit
|
dwdougherty
left a comment
There was a problem hiding this comment.
Looks good to me, but I think somebody with more Node.js experience should take a look. I'll add Docs back into the review fray.
|
I got Claude to have a look and he found the following: Agent builder — JavaScript generation reviewFindings from auditing the JavaScript code generated by the agent builder on the Agent builder page. Templates live in static/code/agent-templates/javascript/; the substitution logic is agent-builder.js:529-560. What was checked: syntax of all 6 generated outputs (2 agent types × 3 models), template substitution correctness, Python ↔ JS parity, and runtime behaviour against Redis 8.2.1 (RediSearch + ReJSON loaded) with the OpenAI SDK stubbed. What was not checked: real LLM calls, the Anthropic and Llama3 variants end-to-end, the full MovieLens dataset (used a 5-movie synthetic seed), or non-OpenAI embedding behaviour. Critical — blocks fresh installs1.
|
- Fix deduplication bug: _getRecentMessages now zips Redis keys with json.mGet results so m._key is the actual key, not undefined - Fix recent-window arithmetic: lTrim and lRange now use RECENT_WINDOW * 2 (user + assistant per turn) instead of * 4 - Fix ft.dropIndex error-string match: replaced brittle catch with ft.info existence check to handle Redis 8 error wording - Fix num_docs field name: ft.info returns num_docs not numDocs, so _indexExists was always returning false and reloading data on every start - Fix Llama3 requiring LLM_API_KEY: default to 'no-key-needed' instead of throwing so local Ollama users don't need a dummy value - Hide Anthropic from JS model selector: JS templates use the OpenAI SDK which is not compatible with api.anthropic.com - Fix Jupyter button: always disabled (feature not yet available) - Fix generic JS fallback: use LLM_API_KEY and node-redis v4 socket shape Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix multi-word genre TAG queries: escape spaces as \\ so 'Science Fiction' matches as a single token rather than two separate terms in RediSearch - Fix reindex leaving stale data: use DD flag on ft.dropIndex to delete movie documents along with the index on reload - Fix Anthropic bypass: processModelSelection now checks allowedModels so typing 'anthropic' or 'claude' while on JavaScript is rejected with a clear message rather than generating broken code - Fix context messages out of order: sort combined recent + semantic results by key (which encodes timestamp) before passing to the LLM - Fix trimmed messages never deleted: evict and delete JSON documents for keys that will fall off the recent window before each lTrim call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix hyphenated genres stripped from TAG query: allow hyphens through the sanitizer and backslash-escape them alongside spaces so Film-Noir matches the stored value instead of becoming FilmNoir - Fix zero minRating silently dropped: use != null instead of truthy check so a minimum rating of 0 is included in the filter query - Fix Llama/Ollama breaking semantic history: add separate embedder client (EMBEDDING_API_KEY / EMBEDDING_API_BASE_URL) that defaults to the LLM values so Ollama users just set EMBEDDING_MODEL=nomic-embed-text with no extra config, matching the pattern used in the RAG templates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I tested both of these in a local environment to make sure they work. |
LLM prompt, validation range, and output label all said 0-10 but MovieLens avgRating is a 0-5 star scale. minRating values above 5 produced filters that matched nothing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hardcoded /code/agent-templates/ breaks deployments under a subdirectory (e.g. staging). Inject window.AGENT_TEMPLATE_BASE via Hugo relURL in the shortcode and use it in agent-builder.js, with /code/agent-templates as the fallback for local/root deploys. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
node-redis field name varies across versions; fall back through num_docs -> numDocs -> '0' to avoid NaN causing every startup to reload the full dataset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
This is Claude's work, btw! I wasn't aware of this until now, but you can ask Claude to post its review findings (or indeed anything else) directly as a comment on the PR. Review of the new JavaScript agent templatesReviewed both new templates ( Main finding — the semantic memory feature is effectively dead code
const evictCount = listLen - (RECENT_WINDOW * 2 - 1);
if (evictCount > 0) {
const toEvict = await this.redisClient.lRange(RECENT_KEY(this.sessionName), 0, evictCount - 1);
if (toEvict.length) await this.redisClient.del(toEvict); // <-- deletes the JSON docs + their index entries
}Because a message's JSON document and its entry in the const seen = new Set(recent.map((m) => m._key));
const extra = semantic.filter((m) => !seen.has(m._key)); // <-- always empty
The eviction was a deliberate choice to bound memory, but it's mutually exclusive with semantic recall. Two coherent options:
I'd lean toward the first, since "semantic message history" is what differentiates this template from a plain recent-window chat. Minor
Verified correct
|
|
Bonus: another more serious issue found by Codex. Follow-up: a second-opinion review surfaced a HIGH-severity data bugAfter the review above, I had a second reviewer (Codex) go over the same two templates independently and verified its most important finding against the real datasets. One new HIGH issue, plus two smaller confirmed ones. 🔴 HIGH —
|
| MovieLens movieId | #ratings | What the join labels it | What it actually is |
|---|---|---|---|
| 356 | 341 | dropped | Forrest Gump |
| 296 | 324 | Terminator 3 | Pulp Fiction |
| 318 | 311 | The Million Dollar Hotel | Shawshank Redemption |
| 593 | 304 | Solaris | Silence of the Lambs |
| 260 | 291 | The 39 Steps | Star Wars |
| 1 | 247 | dropped | Toy Story |
Since popularityScore = ratingCount × avgRating is computed on these misattributed ratings, every recommendation the template produces is wrong — and the most popular films (Toy Story, Forrest Gump, The Matrix, Terminator 2) never appear at all because their TMDB ids aren't present as MovieLens movieIds.
Fix: the join needs the dataset's links.csv (movieId → tmdbId) to bridge the two id spaces before merging metadata with aggregated ratings. This is a code-and-data change, not a one-liner.
Other confirmed items
- MEDIUM —
conversational_agent.js: unescapedsessionNamein the TAG query._getSemanticMessagesbuilds@session:{${this.sessionName}}directly. The constructor accepts arbitrary session names, and TAG metacharacters (spaces,{},|,-) would break the query or match unintended sessions. Escape it the way genres are escaped in the recommendation template. - LOW —
recommendation_agent.js:revenuesort isn't surfaced.validateQueryParamsallowssortBy: "revenue"and the search sorts on it, butrevenueis absent from both theRETURNlist and the formatted output, so "highest revenue" / "blockbuster" requests can't be verified from the result. AddrevenuetoRETURNand the printed lines.
(The eviction-nullifies-semantic-search issue from my first comment still stands as the headline item for conversational_agent.js.)
dwdougherty
left a comment
There was a problem hiding this comment.
Everything looks good in the staged preview. Approved.
Deleting message documents on eviction from the recent list meant the search index only ever contained the same messages already in the recent window, so _getSemanticMessages could never surface anything new. Only lTrim the recent list now; older documents stay in the index for semantic retrieval. Also drop __key from the RETURN fields in _getSemanticMessages — it is not an indexed field and the code uses doc.id instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix ratings joined to wrong movies: ratings_small uses MovieLens ids
but movies_metadata uses TMDB ids; bridge via links.csv before merging.
Without the bridge ~69% of ratings were silently dropped or misattributed
and popular films like Toy Story and Forrest Gump never appeared.
links.csv is now a required dataset (added to download instructions).
- Add revenue to RETURN fields and formatted output so revenue-based sort
requests can be verified from results.
- Escape sessionName before embedding in TAG query to prevent metacharacters
(spaces, {}, |) from breaking the query or matching unintended sessions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sanitizing inline in _getSemanticMessages caused the query to use a different value than what _storeMessage indexed, so KNN recall returned nothing for sessions with special characters. Sanitize once in the constructor so both storage and retrieval always use the same value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_indexExists checked num_docs > 0 which passes for a partial load, leaving the index permanently incomplete. Replace with a sentinel key (movies:load_complete) written only after the full pipeline succeeds, so a crashed import triggers a clean reload on next startup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a09d5ad. Configure here.
… cwd - Delete movies:load_complete sentinel before dropping and recreating the index so a dropped or partially-loaded index always triggers a clean reload rather than leaving indexReady true with no index - Resolve dataset paths with __dirname so the agent finds its CSVs regardless of which directory it is run from Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Node.js agent templates and unlock JavaScript in the agent builder. # Conflicts: # layouts/shortcodes/agent-builder.html

Add conversational and recommendation agent templates using node-redis v4 with Redis Search for vector-based message history and movie indexing. Conversational agent uses hybrid recent+semantic context retrieval, runtime embedding dimension validation, and a clear note on Redis version requirements. Recommendation agent parses genres from MovieLens CSV format into Redis TAGs, validates LLM query params against an explicit allowlist, skips dataset reload if the index is already warm, and filters genres in Redis rather than JS. Fix template URL to use root-relative path so local templates load in dev. Update agent builder to support JavaScript alongside Python.
Note
Low Risk
Changes are limited to static templates, docs, and client-side builder UX; no production services or auth paths are modified.
Overview
JavaScript (Node.js) is now a first-class option in the AI agent builder alongside Python, with docs updated to match.
The builder loads real Node.js templates for conversational and recommendation agents (node-redis, Redis Search/JSON, vector KNN for chat memory; movie dataset ingest, LLM-parsed filters, and Redis TAG genre search for recommendations). Template fetches use a Hugo-injected
AGENT_TEMPLATE_BASEso generated code loads correctly under subdirectory deployments.Wizard flow changes: JavaScript is treated as fully supported (not “coming soon”), model chips and autocomplete omit Anthropic for JS (OpenAI SDK–only templates), and the generic JS fallback uses current redis client
socketoptions andLLM_API_KEY. The Try Jupyter control stays disabled whenever code is shown.Reviewed by Cursor Bugbot for commit 63df428. Bugbot is set up for automated code reviews on this repo. Configure here.