Reference

MCP Tool Reference

Every tool group registered on the han-solo MCP server — what each tool does and which backend it touches. Source of truth for Claude Code and Ren when navigating the tool surface.

16 tool groups, 67 tools accessible to Claude Code. Ren (the Letta agent) has a curated subset of 67 tools attached as of 2026-06-13. All tools require a valid bearer token. The server runs at han-solo-mcp.onrender.com/mcp via Streamable HTTP (stateless). Tool groups are registered at startup in han_solo/server.py — each group lives in its own file under han_solo/tools/. Ren's canonical tool set last updated: 2026-06-13.

Quick index


Architecture

The MCP bridge is a Render-hosted Python service built on FastMCP (the official Python SDK for the Model Context Protocol). It is the only path between Claude Code and Han Solo's memory layer — Letta, the Postgres database, GitHub, and the Anthropic API are all accessed exclusively through this server. Claude never calls those services directly.

Server

FastMCP("han-solo") with stateless_http=True. Endpoint: /mcp (Streamable HTTP). DNS rebinding protection is disabled — bearer token auth is enforced instead. Render auto-deploys on every push to main.

Authentication

Every request requires Authorization: Bearer $MCP_TOKEN. The bridge supports three users with separate tokens: USER_TOKEN_SCOTT (owner), USER_TOKEN_TED (collaborator), USER_TOKEN_REN (agent). Pattern: USER_TOKEN_{USER_ID.UPPER()}. Tokens must be set in two places: ~/.zshenv AND the LaunchAgent plist EnvironmentVariables — updating one but not the other causes intermittent auth failures depending on how Claude Code was launched. The same two-place rule applies to LETTA_API_KEY.

Auth-exempt paths: /health, /, /chat, /workspace, /api/jobs-status, /api/code/webhook. GET-exempt: /api/session-logs, /api/transcripts, /api/verify-runs/latest, /api/usage/stats, /api/session-summaries, and the /api/transcripts/, /api/session-summaries/, and /docs/ prefixes.

Tool registration

16 tool groups register at startup via memory.register(server), signals.register(server), etc. in han_solo/server.py. Deploying new tool code to Render does NOT automatically register those tools into Letta's agent registry — that requires a separate POST /api/admin/sync-mcp-tools call after every deploy.

Ren's tool set

Ren (the Letta agent) has 67 tools attached as of 2026-06-13. These are a curated subset of the full MCP surface — not every tool Claude Code can call is available to Ren. The canonical set is defined in han_solo/letta_client.py as CANONICAL_REN_TOOL_NAMES and restored on every server startup via ensure_ren_tools().

Two letta_core built-ins must be explicitly attached — they are not discovered automatically by the MCP registration flow: send_message and archival_memory_search. Despite being built-ins, they do not auto-attach when tools are restored. Both are in CANONICAL_REN_TOOL_NAMES and attached by ensure_ren_tools().

archival_memory_insert is intentionally absent from the canonical set — governance question open. Ren cannot insert archival passages directly. Comment in letta_client.py: "search first, insert later." Unresolved as of 2026-06-01.

Lifespan and startup

On startup: an httpx.AsyncClient is initialised, Ren's agent ID is resolved, ensure_ren_tools() runs to restore any dropped tools, and the asyncpg DB pool is opened. The FastMCP session manager and these initialisation steps run as a combined lifespan via contextlib.asynccontextmanager.

Agent ID resolution order: checks REN_AGENT_ID env var first (fastest — skips Letta lookup entirely; pinned in Render env vars for ren-v2). If not set, falls back to get_or_create_ren_agent(REN_AGENT_NAME).

Startup failure is non-fatal. If agent ID resolution or ensure_ren_tools() fails, the server logs 'Failed to initialise Ren agent (will retry on next request)' but does NOT crash. /health can return ok or degraded even when Ren has no resolved agent ID — a green health check does not guarantee Ren is reachable.

CORS and middleware

CORS wraps auth so preflight OPTIONS requests are handled before auth. Allowed origins: han-solo-docs.pages.dev, han-solo-mcp.onrender.com, han-solo-jottings.pages.dev, localhost:5173. CORS is outermost; BearerAuthMiddleware is inner. BaseHTTPMiddleware was avoided — it breaks SSE streaming.

Note: allow_origins also includes the literal string 'null' (intentional). This permits file:// origin requests — browsers send Origin: null when opening local HTML files. Not a misconfiguration, but worth knowing during security review.

How the bridge works

Understanding the request path helps diagnose failures and reason about tool behaviour under load.

Request path (tool call from Claude Code): Claude Code → han-solo-mcp.onrender.com/mcpBearerAuthMiddleware validates token → FastMCP routes to registered tool function → tool calls Letta and/or asyncpg → response returned synchronously.

Circular dependency rule. Any MCP tool that calls back into Letta's message queue (/v1/agents/{id}/messages) while Letta is waiting for that tool's response will deadlock. Two tools fell into this trap in May 2026: get_session_brief (4 Letta API callbacks) and search_signals (called letta.search_passages()). Both were removed from Ren's canonical set. The stress-test signature is alternating success/failure at 1-second intervals — not gradual degradation. Any new tool that makes outbound calls to han-solo-letta.onrender.com must be treated as a potential deadlock risk.

Failsafe channel. POST /api/admin/failsafe-message is a completely separate code path from the normal tool pipeline. Commands: PING, STATUS, DUMP_MEMORY, RELOAD_BRIEF. If the main MCP pipeline is broken, the failsafe still works. The two paths must never be merged or routed through the same middleware.

Health check endpoint. GET /health — no auth required. Returns {"status": "ok", "ren_agent": "...", "db": {...}} when both the Letta agent ID is resolved AND the asyncpg pool is connected. Before 2026-05-26, this endpoint only checked the agent ID, giving false confidence when the DB pool was down. Now it checks both. Edge case: if startup agent init fails non-fatally, the server runs but may return degraded — a green health response does not guarantee Ren is reachable. check_system_health checks five services in parallel (letta, voyage_ai, anthropic, mcp_bridge, core_memory) and is the more complete diagnostic — use it over raw /health when diagnosing Ren-specific issues.

write_core_memory notification removal. An older version of write_core_memory called send_chat_message() after the write to notify Ren. That notification routed through Letta's message queue — the same circular path as get_session_brief and search_signals. It was removed. Standing rule: Claude Code uses the bridge (send_to_ren) to reach Ren before writing to her core blocks during a live session. Ren writes her own blocks. Never route notifications through write_core_memory.

Code search rate limit. GET /api/code/search embeds queries via OpenAI — uncapped, this burns quota fast. Rate limit: 20 requests per 60 seconds per client IP. In-memory only (no Redis). Intentional for a single-process Render service.

Known failure modes

Real incidents

Incident A — May 2026 — Circular dependency deadlock

get_session_brief and search_signals both made outbound calls back into Letta's API while Letta was holding the connection open waiting for their MCP response. Under load this exhausted the connection pool. Symptom: intermittent "Ren could not be reached" errors in the workspace UI, alternating success/failure pattern. Diagnosis took two full sessions — cold start was blamed first incorrectly. Fix: both tools removed from the canonical set. Impact: ~3 days of intermittent failures.

Incident B — May 2026 — Tool wipe recovery (3–4 hours)

While trying to fix tool registration issues, all 16 Letta tools were deleted via the Letta API directly. Ren had zero capability. Recovery required manual re-addition of each tool via direct PATCH to the Letta API. Root cause: using the wrong endpoint. Correct path is POST /v1/tools/mcp/servers/{server}/{tool_name} per tool — not bulk delete and re-add.

Incident C — May 2026 — 4 tools deployed but not registered

delete_notecard, append_t4_entry, update_open_threads, delete_archival_passage were deployed to the MCP server but not registered into Letta's registry. Ren could not call them — no error, they just were not in her tool list. Found during SL-007 regression testing. Fix: built /api/admin/sync-mcp-tools, registered individually, confirmed via agent-info. Post-deploy sync step permanently added to docs/add-tool-checklist.md.

Incident D — May 2026 — Search spiral (unified search tool)

A combined search tool was added that merged archival memory search with code wiki search into a single MCP call. This unified tool triggered SEARCH FIRST logic but had no clear intent — it could not distinguish between searching code or searching memory, causing cascade triggering and spiral loops where each search result triggered another search. Fix: reverted to separate search_t4 and search_code tools. Standing rule established: different search types require different gating. Conversational research (search_t4, search_transcripts) is autonomous. Code search is collaborative and intentional. Never merge them.

Danger zones & known issues

Documented failure patterns and gotchas that have burned real time. Read this section before diagnosing any unexpected bridge or tool behaviour.

Adding a new tool — required steps

The post-deploy sync step has been skipped twice and caused silent capability gaps both times. Read docs/add-tool-checklist.md in full (9 steps) before starting. The mandatory steps after any deploy that adds new tools:

  1. Add the tool function to the appropriate file in han_solo/tools/ and call server.tool() via the register() function — same pattern as every other tool in that file.
  2. If the tool should be available to Ren, add its name to CANONICAL_REN_TOOL_NAMES in han_solo/letta_client.py. If not adding to canonical, document why in a comment next to the name.
  3. Push to main. Render auto-deploys — wait for the deploy to complete before proceeding.
  4. POST /api/admin/sync-mcp-tools (with Authorization: Bearer $MCP_TOKEN header). This calls GET /v1/tools/mcp/servers/han-solo/tools (live MCP call), registers missing tools individually via POST /v1/tools/mcp/servers/han-solo/{tool_name}, then runs ensure_ren_tools(). A clean response looks like {missing_found: [], registered: [], errors: []} — meaning tools were already registered from a previous sync. Note: ensure_ren_tools() fires at startup and attaches everything in the canonical set that is already in Letta's registry — Letta does NOT automatically discover new MCP tools after initial server connection. New tools must be explicitly registered into Letta's registry before ensure_ren_tools() can attach them.
  5. Wait 10–15 seconds for Letta's MCP discovery to complete before checking.
  6. Update scripts/verify.py: add the tool name to EXPECTED_HAN_SOLO_TOOLS. If the tool is destructive, also add it to ALLOWED_WRITE_TOOLS_ON_REN.
  7. GET /api/admin/agent-info — confirm the new tool name appears in the agent_tools list and the count is expected (67 + any newly added tools). If not present after 15 seconds, wait another 15 and re-check before concluding registration failed.
  8. Test the tool via send_to_ren before declaring done.
  9. Open a fresh Claude Code session before testing the new tool from Claude Code. MCP tools are registered at session start. A tool added and synced in the current session will NOT appear as callable in that same session — it is not a bug, not a registration failure. Open a new session and the tool will be available.

Diagnostic order if tool calls silently fail or return empty from Claude Code: (1) check bridge health at /health(2) check Letta is reachable → (3) verify token is valid → (4) confirm tool is registered via agent-info. In that order. Do not go straight to destructive Letta API operations.


Memory

Source: han_solo/tools/memory.py

Read and write Letta core memory blocks — the always-loaded context blocks that travel with every Ren prompt. Also manages archival passage lifecycle and open threads.

Tool What it does Backend
read_core_memory Read a named core memory block by label (e.g. always_loaded_core, pending_thoughts, project_state). Returns the block value as a string. Letta
write_core_memory Full-replace a named core memory block. Protected blocks (user_registry, api_keys, etc.) require owner-level access and cannot be written by Claude. Letta
list_core_memory_blocks List all core memory blocks with label, character count, and a 100-char preview. Used at session start to survey memory state. Letta
check_memory_capacity Check the pending_thoughts block fill level before writing. Returns {current_chars, block_limit, pct_full, status} where status is ok / warning / critical. Prevents silent overflows that break the nightly dream script. Letta
log_memory_access Record every archival memory search for access pattern analysis (the Memory MRI). Logs the query, which passage IDs were returned, and whether any were used. Builds the dataset that surfaces cold passages and dry wells. Postgres
delete_archival_passage Delete an archival passage by Letta passage ID. Two-step: call with confirmed=False to flag, then confirmed=True after Scott approves. Permanent — no undo. Letta
update_open_threads Add, close, or remove items from Ren's open_threads core memory block. Action values: add / close / remove. Matching on close/remove is case-insensitive substring. Letta
enrich_passage Record context about how an archival passage was used during retrieval (memory reconsolidation). Context notes accumulate — each append is preserved so future retrievals carry richer context. Postgres

Signals

Source: han_solo/tools/signals.py

Write and search the signal taxonomy — typed observations about people and work that flow into portrait and archival memory. Four signal types: relational, directional, ren, texture. Five subjects: scott, ted, ren, project, framework.

Tool What it does Backend
write_signal Write a typed signal to Letta archival memory. Texture signals have no noise filter — write small things as they happen. Validates signal_type and subject before writing. Also dual-writes to the signals Postgres table for structured querying. Letta Postgres
search_signals Semantic search across archival passages tagged as signals. Optional filters for signal_type and subject. Returns type, subject, date, and content for each match. Letta
write_session_summary Write a structured session-close summary to archival memory. The raw material the nightly dream script processes. Expected structure: date/participants, decisions, relational signals, texture signals, Ren self-observations, pending thoughts, open threads. Letta

Session Brief

Source: han_solo/tools/brief.py

The Solo Hook (framework-skill-inject.sh) calls get_session_brief before every Claude Code session, injecting Ren's pending thoughts and always-loaded context automatically.

Tool What it does Backend
get_session_brief Retrieve the session opening brief: always-loaded core context, pending thoughts from last session, the 5 most recent archival signals, and current date/time in CT. Called at session start by the hook — Claude begins every session already oriented. Letta
write_pending_thoughts Full-replace the pending_thoughts core block. Called at session close. Ren's concerns, ideas, and connections for the next session. Check capacity with check_memory_capacity before writing large entries. Letta
check_memory_health Check memory system health: failed T1→T2/T2→T3 tier promotions in the last 24 hours and transcript capture status. Flag anything non-zero to Scott immediately. Call at every session start. Postgres

Phase

Source: han_solo/tools/phase.py

Phase gate tools — read and advance project state stored in the project_state core memory block as JSON. Gates block advancement until prerequisites are met.

Tool What it does Backend
get_project_state Read current project state: active phase, completed gates, artifact inventory, current slice, and open amendments. Source of truth for phase awareness. Letta
check_phase_gate Check whether a transition to a target phase is currently allowed. Returns {allowed, reason, missing}. Valid target phases: discover, tech_context, shape, prd_to_plan, build_slice_1, build_next, deploy. Letta
advance_phase Advance project state to a target phase. Blocked if gate prerequisites are unmet — returns the blocking reason. Records phase history with actor and timestamp. Letta
record_artifact Record that a phase artifact exists, unlocking downstream phase gates. Example keys: brainstorm_artifact, backlog, shape_document, previous_slice_qa_cleared. Letta

Portraits

Source: han_solo/tools/portraits.py

Living portrait tools — Ren's evolving interpretations of Scott, Ted, and herself, stored as core memory blocks with two layers: forming (emerging, held lightly) and trusted (confirmed across repeated observation).

Tool What it does Backend
read_portrait Read a portrait layer for a person. Returns Ren's prose interpretation, not a summary. person: scott | ted | ren. layer: forming | trusted. Letta
write_portrait Full-replace a portrait layer. Prose in Ren's voice — not bullet points. Only update the trusted layer when a pattern has been observed enough times to confirm. Letta
add_portrait_signal Append a single dated observation to a person's forming portrait. One specific, non-obvious thing noticed this session. Also sends Ren a live system notification so she can incorporate the signal without waiting for the next session. Letta
read_all_portraits Read all portrait blocks for Scott, Ted, and Ren — both forming and trusted layers — in a single call. Returns {person: {layer: content}}. Used at session open for full relational context. Letta

Notecards

Source: han_solo/tools/notecards.py

Low-ceremony mid-session captures. A notecard is not a task and not a thread — just text, who wrote it, when, and where it came from. Statuses: active, completed, archived.

Tool What it does Backend
create_notecard Create a notecard. Priority 1–5 (default 3). Source: chat (mid-session) or manual. Returns the card ID. Postgres
update_notecard Update a notecard's status, text, and/or priority by ID. At least one field required. Status options: active, completed, archived. Postgres
delete_notecard Delete a notecard with a two-step approval flow. Call with confirmed=False first — marks as pending_deletion and returns content for Scott to review. Call with confirmed=True to permanently delete. Postgres
list_notecards List notecards by status. Omitting status returns active + completed. Returns id, text, creator, status, source, session_id, created_at. Postgres

T4 Project Memory

Source: han_solo/tools/t4.py

T4 is the persistent project memory store — framework artifacts produced across the Solo Builder Framework build cycle, organized by project slug and entry type. Three write behaviors: write_once, upsert, append — derived from entry type, never specified by the caller.

Tool What it does Backend
write_t4_entry Write a T4 artifact. Hierarchy types (phase, deliverable, slice) require plain_language and technical_description. Slices accept four anchors (design, data, done, process) and a quality contract. Write behavior is auto-derived from entry type. Postgres
get_t4_entry Fetch a T4 artifact by project, entry_type, and entry_id. Auto-ID types (e.g. tech_context, decisions_log) don't require an explicit entry_id. Returns full content or an error dict. Postgres
search_t4 Keyword search across T4 artifacts for a project. Optional entry_type filter. Returns matching entries with an excerpt (first 300 chars) and updated_at timestamp. Postgres
delete_t4_entry Delete a T4 entry by project, type, and ID. Omitting entry_id deletes ALL entries of that type for the project. Returns the count of deleted rows. Postgres
list_t4_projects List all T4 projects with owner, visibility, current_phase, and slice progress counts (total/done). Use at session start before assuming what projects exist. Postgres
append_t4_entry Force-append to any T4 entry regardless of its normal write behavior. Each append is timestamped and separated with a divider. Creates the entry if it doesn't exist. Use for mid-session notes on handoffs or additions to decisions logs. Postgres

Skills

Source: han_solo/tools/skills.py

Framework skill tools — the skills table in Postgres is the authoritative source of Solo Builder Framework phase skill content, populated by the sync pipeline and injected into Claude Code sessions via the hook on every UserPromptSubmit.

Tool What it does Backend
get_skill Read a framework phase skill by slug. Returns {phase_slug, layer, content, updated_at}. Common slugs: discover, solo-build, design-sprint, deploy, and others. Postgres
write_skill Create or update a framework phase skill. Minimum 50 chars — rejects suspiciously short content. Defaults to layer phase-active. Use after editing a SKILL.md in ~/Developer/Framework Vers1/skills/. Postgres
verify_skills_sync Compare Framework Vers1 skill files on disk against Han Solo DB records using SHA-256 content hashes. Returns {in_sync, diverged, missing_from_db, missing_from_disk}. Flag diverged entries to Scott — never auto-fix. Postgres
list_skills List all framework phase skills in Han Solo — slug, layer, content length, and last update. Use before calling get_skill to discover available slugs. Postgres

Logbook

Source: han_solo/tools/logbook.py

Session log entries — what happened, decisions made, open threads, and commit references. Two levels: major (architecture changes, significant decisions) and minor (routine work).

Tool What it does Backend
write_session_log Write a session log entry to the Han Solo project logbook. Required: summary (3–5 sentences, plain language). Optional: decisions, open_threads, commit_refs (comma-separated), tags (comma-separated system names), level, session_date. Postgres

Transcripts

Source: han_solo/tools/transcripts.py

Read-only search across parsed Claude Code session transcripts stored in Postgres by the parse_transcripts.py LaunchAgent. Sessions from the last 45 days. No Anthropic API calls — raw session history only.

Tool What it does Backend
search_transcripts Search parsed Claude Code session transcripts by keyword. All words must match. Returns session IDs, timestamps, entry counts, and a content preview. Limit default 10, max 50. Postgres

Bridge

Source: han_solo/tools/bridge.py

Synchronous communication from Claude Code to Ren. Messages are prefixed with [FROM CLAUDE CODE] so Ren knows the channel. The exchange lands in Ren's Letta conversation history — she remembers it. Token usage is logged to Postgres via db.write_usage_log("bridge", ...) as a background task. Does not appear in the workspace UI chat. Added 2026-05-24.

Tool What it does Backend
send_to_ren Send a message to Ren synchronously and return her response. Use for architecture decisions before implementing, assumption audits, continuity checks ("why did we build it this way?"), and session-close handoff notes. Token usage is logged. Letta Anthropic API

Codebase

Source: han_solo/tools/codebase.py

Codebase reading and semantic search. Embeddings are stored in the code_chunks Postgres table (OpenAI text-embedding-3-small), re-indexed automatically on push to main via a GitHub webhook (POST /api/code/webhook, HMAC-SHA256 signature validated). HTML files are excluded from search results — they are docs artifacts, not implementation code. The /api/code/search endpoint is rate-limited to 20 requests per 60 seconds per client IP (in-memory, intentional for single-process Render).

Tool What it does Backend
read_github_file Read a live file from the GitHub repo (not the indexed Code Wiki). Use to verify current implementation before making decisions, or read a file not yet indexed. Supports any public repo. Uses GITHUB_TOKEN if set (5,000 req/hr); falls back to unauthenticated (60 req/hr). GitHub API
fetch_url Fetch live content from any public URL. Use for reading live docs pages, verifying a deploy is live, reading Letta release notes, or any public HTTP resource. Content truncated at 50,000 chars. No auth header support. HTTP
search_code Semantic search across the han-solo codebase by intent. Embeds the query via OpenAI, computes cosine similarity in-memory against indexed chunks. Use to understand how something works, find where a behavior is implemented, or trace impact of a change. Limit default 5, max 10. Postgres OpenAI

Health

Source: han_solo/tools/health.py

System health checks — five services checked in parallel: letta (agent reachable, all canonical tools attached), voyage_ai (embedding service connectivity), anthropic (API key configured, /v1/models reachable), mcp_bridge (external self-ping at han-solo-mcp.onrender.com/health — localhost self-ping fails on Render due to network isolation), and core_memory (reads pending_thoughts block from Letta). Each check has a 5-second timeout. Writes and resolves alerts in Postgres as a background task. Alert threshold: fewer than 67 canonical tools attached triggers a critical tool_count alert (as of 2026-06-13).

Tool What it does Backend
check_system_health Full health check across Letta, Voyage AI, Anthropic API, MCP bridge, and core memory. Checks run in parallel with a 5-second timeout per service. Returns {all_healthy, services, tools_attached, recommendations, timestamp}. If not all_healthy: surface to Scott immediately, pause work, troubleshoot together. Also writes/resolves alerts in Postgres as a background task. Letta Postgres Anthropic API
verify_agent_tools Diff Ren's currently attached Letta tools against the canonical expected set. Returns {attached, missing, extra, canonical_count, attached_count, all_match}. Use at session start and after any tool sync operation — silent tool drops are not visible until Ren tries to call a missing tool. Letta

Hub

Source: han_solo/tools/hub.py

Hub component health verification before slice gates. The hub tracks 9 system components, each seeded with vitals, incidents, danger zones, and assumptions from T4 knowledge base entries.

Tool What it does Backend
check_hub_health Verify all hub components are seeded with vitals, danger zones, and assumptions. Use before approving any slice gate — zero counts on danger_zones or assumptions means the component is not fully seeded and the gate should not open. Returns {components, all_seeded, unseeded}. Postgres

Gates

Source: han_solo/tools/gates.py

Gate artifact tools — approve slice gates and generate permanent HTML snapshots. The approval marker is a prerequisite for artifact generation; the artifact is then served at a stable URL.

Tool What it does Backend
approve_gate Append a Scott approval marker to the T4 gate brief entry for a slice. Call immediately after Scott says yes in chat. The marker is required before write_gate_artifact will render an HTML artifact. Idempotent — safe to call if already approved. Postgres
write_gate_artifact Generate a permanent HTML artifact from an approved T4 gate brief. Checks for the Scott approval marker before rendering. Stores artifact HTML as a gate_artifact T4 entry. Returns the stable URL at /api/gates/{project}/{slice_id}/artifact. Postgres

Claude Plus

Source: han_solo/tools/claude_plus.py

Claude Plus session activation — fetches all 10 persistent blocks Claude Plus needs at session start to load its operating context, state machine, and pending work. All data lives in dedicated Postgres tables separate from Ren's memory.

Tool What it does Backend
get_claude_plus_blocks Fetch all 10 Claude Plus blocks in a single call: identity_and_role, operating_contract, hub_snapshot, session_shape, slice_sizing_log, state_machine, session_close, pending_decisions (open items only), failure_recovery, and framework_state. Returns each block's content and metadata. Postgres

Jottings REST API

Source: han_solo/server.py (lines 1649–2276) — these are REST endpoints, not MCP tools

The same Render process that hosts the MCP server also serves a full Jottings REST API — approximately 20 endpoints. These are not accessible to Claude Code as MCP tools; they are called by the Jottings frontend (han-solo-jottings.pages.dev). Auth is required except as noted. S3 uploads are limited to 10 MB and accept jpeg, png, gif, and webp only.

Endpoint What it does Backend
POST /jottings/topics Create a new topic. Postgres
GET /jottings/topics List all active topics. Postgres
GET /jottings/topics/{id}/pages List all pages for a topic. Postgres
POST /jottings/pages Create a new page. Postgres
GET /jottings/pages List pages, optionally filtered by topic. Postgres
PUT /jottings/pages/{id} Autosave page content (title + body). Called on keystroke debounce from the editor. Postgres
POST /jottings/pages/{id}/replies Add a reply to a page. Postgres
GET /jottings/pages/{id}/replies List replies for a page. Postgres
POST /jottings/upload Upload an image to S3. 10 MB limit, jpeg/png/gif/webp only. Returns a public S3 URL for embedding in page content. S3
POST /jottings/search Full-text search across pages and replies. Postgres
PATCH /jottings/topics/{id} Soft-archive a topic (sets archived flag, does not delete). Postgres
PATCH /jottings/pages/{id} Soft-archive a page. Postgres
DELETE /jottings/topics/{id} Hard-delete a topic including all associated S3 assets. Permanent. Postgres S3
DELETE /jottings/pages/{id} Hard-delete a page including all associated S3 assets. Permanent. Postgres S3

Backend key

BadgeWhat it means
Letta Reads or writes Ren's Letta agent — core memory blocks, archival passages, or conversation history. Touches han-solo-letta.onrender.com. Every Letta call is a round-trip that may trigger Anthropic API inference internally.
Postgres Reads or writes the han-solo-db PostgreSQL 16 database directly via the asyncpg connection pool. No Letta involvement. Covers T4, notecards, signals, transcripts, logbook, skills, notecards, code chunks, hub tables, and Claude Plus tables.
Anthropic API Makes a live request to the Anthropic API — either a connectivity check or an actual model inference call (via Letta's bridge path). Pay-per-token.
OpenAI Calls OpenAI's embeddings API (text-embedding-3-small) to embed a query for semantic search. Used by search_code. Requires OPENAI_API_KEY env var.
GitHub API Reads file content from api.github.com. The han-solo repo is public so unauthenticated reads work (60 req/hr); GITHUB_TOKEN raises that to 5,000 req/hr.
S3 AWS S3 bucket used by the Jottings API for image uploads and hard-delete cleanup. Requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and S3_BUCKET_NAME env vars. Not used by any MCP tools — Jottings REST API only.