Runbook — Legacy

Attaching a Tool to Ren

Verified against han_solo/letta_client.py and han_solo/server.py on 2026-06-09. Every step traced to code, not memory.

Superseded. This is the original runbook. The comprehensive guide with all danger zones lives at add-tool-to-mcp-and-ren.html.

How it works

Ren's tool set is declarative, driven by one source of truth: the CANONICAL_REN_TOOL_NAMES set in han_solo/letta_client.py (line 17). On every server startup — and on demand — ensure_ren_tools() forces Ren's actual Letta attachments to match that set exactly (adds missing, removes extras). You don't attach tools by hand; you edit the canonical set and let the sync enforce it.

The steps

1

Confirm the tool is live on the MCP server

The tool must already be implemented and registered on the han-solo FastMCP server (code in han_solo/tools/). Quick check: call it, or hit GET /api/admin/list-mcp-tools (server.py:107). If it returns, it's live.

2

Add the tool name to the canonical set

Edit han_solo/letta_client.pyCANONICAL_REN_TOOL_NAMES (line 17). Add the exact tool-name string.

  • tool_rules are auto-derived by canonical_ren_tool_rules() (line 63): every tool gets continue_loop automatically except those in REN_EXIT_LOOP_TOOLS (line 60), currently only send_message.
  • ⚠️ Only add a name to REN_EXIT_LOOP_TOOLS if the tool is meant to END Ren's turn. Otherwise leave it out — normal tools must be continue_loop or her agent loop dies after one call (the step_count=1 bug).
3

Commit and push to main

han_solo/ is the Render-deployed package. Pushing triggers an auto-deploy.

4

Deploy completes → auto-sync on startup

On boot, the lifespan hook (server.py:2977 _lifespan → line 2989) calls ensure_ren_tools(), which re-syncs Ren's attachments to the new canonical set. In most cases this alone completes the attach.

5

(If needed) Force the sync without a restart

POST /api/admin/sync-mcp-tools (server.py:2922). The handler (server.py:148) runs sync_mcp_tools() — discovers MCP tools and registers any missing into Letta's registry — then ensure_ren_tools() to attach. Idempotent; safe to call more than once.

6

Verify it took

  • Call verify_agent_tools() → check all_match: true and that attached_count reflects the new total.
  • Or check Render logs for Ren tools+rules synced — {N} tools, {N} rules.

The gotchas (from the code, not memory)

A — tool_ids and tool_rules must go together. ensure_ren_tools() sends PATCH /v1/agents/{id} with both (letta_client.py:278). Letta's PATCH nulls any omitted field (comment line 291). The function handles this; never hand-roll a PATCH that sends only one.
B — never include llm_config in that PATCH. Comment lines 268–270: including llm_config in the same PATCH "silently drops letta_memory_core tools."
C — the tool must be in Letta's registry first. If a canonical name isn't registered, ensure_ren_tools() skips it and logs Tools not found in Letta registry: … (line 328). Step 5's sync_mcp_tools() prevents this.
D — circular-call deadlock (the big one). A tool that calls Letta back mid-execution while Letta waits on the MCP response will deadlock Ren. Two tools were removed for exactly this (lines 49–50): get_session_brief (called Letta back 4×) and search_signals. Before attaching any tool, check its implementation for a callback into Letta's agent loop (e.g. letta.send_chat_message(...)). Reading/writing core blocks via the management API (letta.read_core_block / letta.write_core_block) is safe — that's how read_core_memory / write_core_memory already work.

Worked example: the five portrait / Maya tools (2026-06-09)

ToolData pathSafe to attach?
read_portraitletta.read_core_block (management API)✅ Clean
read_all_portraitsletta.read_core_block (management API)✅ Clean
write_portraitletta.write_core_block / create_core_block✅ Clean
read_maya_historydb.list_sessions_by_agent (pure DB read)✅ Clean
add_portrait_signalreads/writes core block then letta.send_chat_message()⛔ Gotcha D

add_portrait_signal injects a chat message into Ren's own agent (portraits.py), with hardcoded text "Claude just added a portrait signal…". If Ren calls it on herself mid-turn it risks a deadlock and misattributes authorship. Fix required before attaching: make it caller-aware — skip the self-notification (and correct the attribution) when Ren is the caller. Then add its name to the canonical set.

Residual unknown (honest): Not yet confirmed whether the five tools are currently in Letta's registry vs. only on the MCP server — but step 5's sync_mcp_tools() registers any that are missing, so the runbook covers it either way.
Source of truth for the canonical roster: han_solo/letta_client.pyCANONICAL_REN_TOOL_NAMES. Related: notecard 119 (verify.py EXPECTED_HAN_SOLO_TOOLS drift), notecard 125 (memory_landscape tool-roster reconciliation). Verified 2026-06-09.