docs(letta): document Letta-backed agent mode + the streaming usage caveat - #130
Conversation
…aveat
The Letta bridge (gateway/letta_brain.py + dispatch in
_run_agent_inner) has been merged for a while, but `git grep -i letta`
over '*.md' returned nothing — every piece of documentation was silent
about the second runtime. Anyone told that Hermes fronts heterogeneous
runtimes found no trace of how to enable it or where it stops short.
Adds:
- website/docs/user-guide/features/letta-brain-mode.md — the full
contract: env/config wiring, where the bridge sits in the pipeline
(after authorization/pairing/group approval, before proxy mode and
the native loop), the B1.5 sender tag, streaming fallback rules,
per-agent serialization, persistence/audit behavior, limitations.
- README "Alternate Runtimes" section comparing native / Codex /
Letta / proxy, with the caveat inline.
- LETTA_BRAIN_{URL,AGENT_ID,API_KEY,STREAMING} in the env var reference.
- Sidebar entry next to the Codex app-server runtime.
The load-bearing caveat, stated in all three places: streaming is ON
unless LETTA_BRAIN_STREAMING is explicitly off/0/false/no, and
stream_message parses only assistant-text deltas — Letta's terminal
usage_statistics event is never captured. So streamed brain turns
record last_prompt_tokens=0, and context-window tracking, compression
decisions, and the runtime footer all read 0 for them. Real counts come
back only from the blocking client, which is the only path the
audit-parity tests exercise (they set LETTA_BRAIN_STREAMING=off).
Separately, credits/cost tracking lives in the native loop and never
runs for brain turns at all, and Letta's tool calls don't reach Hermes'
tool audit log — both explicit non-goals today, documented as such.
Docs only. No behavior change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the native loop Audit correction. The page claimed: "If the URL and agent ID are missing when a turn arrives, the gateway replies with `⚠️ Letta brain not configured (...)` rather than silently falling back to the native loop." That is backwards. Dispatch in `_run_agent_inner` (gateway/run.py:18631) is gated on `if self._get_letta_brain():` — the *same* check the guard inside `_run_agent_via_letta` (18032-18040) repeats. When either LETTA_BRAIN_URL or LETTA_BRAIN_AGENT_ID is absent, `_get_letta_brain()` returns None, the branch is skipped, and the turn proceeds to proxy mode or the native AIAgent loop. The⚠️ string is effectively unreachable via the gateway path, and the fallback is exactly the silent one the sentence promised it wasn't. This matters more than a wording slip: a typo in LETTA_BRAIN_AGENT_ID yields a normal-looking native Hermes turn, so none of this page's caveats apply to it while the native toolset — possibly not intended for that door — does. - letta-brain-mode.md: replace the sentence with a `:::warning` that states the real behavior, names the log line to confirm a live binding, and explains why the guard string never fires; add a Known-limitations bullet. - README.md: one sentence in the Letta subsection (both vars required, silent fallthrough otherwise). - environment-variables.md: same note on the LETTA_BRAIN_AGENT_ID row. Docs only. No behavior change. The streaming/budget caveat is unchanged — it was independently verified correct against origin/main (letta_brain.py:285-288 and 44-45; run.py:18086-18087; test_letta_audit_parity.py:116). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Independent auditRe-verified every load-bearing claim against Verified correct (the caveat holds)
❌ One error found and fixed (071c693)The page claimed:
This is backwards. Dispatch at This is worth more than a wording fix: a typo in Merge conditions
Merging on that basis. The streaming/budget caveat is prominent in three places and, as far as I can verify, accurate. |
Why
git grep -i lettaoverorigin/mainfor*.mdreturned nothing. The Letta bridge is genuinely merged —gateway/letta_brain.pyplus a dispatch branch at the top of_run_agent_inneringateway/run.py— with two test modules behind it, but no documentation anywhere. Anyone told that a Hermes gateway can front a second, heterogeneous runtime had no way to find out how to turn it on, what reaches the Letta agent, or where the integration stops short.Docs only. No behavior change.
What's here
website/docs/user-guide/features/letta-brain-mode.mdREADME.mdwebsite/docs/reference/environment-variables.mdLETTA_BRAIN_URL,LETTA_BRAIN_AGENT_ID,LETTA_BRAIN_API_KEY,LETTA_BRAIN_STREAMINGwebsite/sidebars.tscodex-app-server-runtimeStated prominently in all three places, because a launch narrative is likely to lean on "a Letta-brained door inherits Hermes' whole policy stack." Most of it does. Budget accounting does not.
stream_message(gateway/letta_brain.py:285-288) parses only assistant-text deltas off the SSE feed. Letta's terminalusage_statisticsevent is never captured, so a streamed turn reportsprompt_tokens = 0/completion_tokens = 0— documented in theLettaTurndocstring at lines 44-45.gateway/run.py:18086-18087readsLETTA_BRAIN_STREAMINGand only takes the blocking path when the value isoff/0/false/no. So zero-token turns are the default, not an edge case._run_agent_via_lettarecordslast_prompt_tokens: 0for those turns, so context-window tracking, compression decisions, and the runtime footer all read 0.LETTA_BRAIN_STREAMING=off(tests/gateway/test_letta_audit_parity.py:116) — that is exactly the scope of the guarantee.run_agent.py), which brain turns never enter, so there is no cost/spend accounting for them at all. Letta's tool calls likewise don't reach Hermes' tool audit log. Both are explicit non-goals in the test module's docstring, and the page says so rather than implying coverage.The page's summary line: the door's authorization and audit story holds; its cost-control story does not.
Code facts verified before writing
Everything documented was read off
origin/main, not inferred:_run_agent_inner(gateway/run.py:18628-18640), checked before proxy mode, reusing the proxy-mode result-dict shape and_setup_platform_stream_consumer. Authorization runs upstream at_is_user_authorized(~line 9668) →_handle_message_with_agent(11582) →_run_agent(12489) →_run_agent_inner(18600), so the bridge is not an authorization bypass._get_letta_brain(17862): env first (LETTA_BRAIN_URL+LETTA_BRAIN_AGENT_ID, optionalLETTA_BRAIN_API_KEY), thengateway.letta_brain.{base_url, agent_id, api_key}. Mode activates when both URL and agent ID are present; no separate flag.build_sender_tag/apply_sender_tagprepend[from Alice in #family]. DMs get sender only; group/channel/thread also getschat_name, falling back to{chat_type}:{chat_id}(gateway/run.py:18069-18072).agent_persisted: Falseso the gateway writes transcript rows itself; the user message is persisted even on a failed round-trip.asyncio.Lockkeyedbase_url|agent_id, with generation re-checks after the lock, mid-stream, and post-turn.Honesty notes
The page is explicit that this is a productionized prototype and lists what it does not do: one agent per gateway (no per-platform/group/profile routing); Hermes personality, memory, context files, skills, and toolset are not forwarded; no per-sender isolation on the Letta side; the SSE path has unit coverage for line parsing only (no end-to-end streaming test); superseding a turn suppresses delivery but does not cancel the Letta server's in-flight work; the 120s timeout is not configurable. No capabilities were invented.
Before merging
origin/mainand touches onlyREADME.md,website/sidebars.ts,website/docs/reference/environment-variables.md, and one new docs page — but please rebase onto currentmainbefore merging in case that session has landed anything.docs-site-checks(ascii-guard lint --exclude-code-blocks docs+ a Docusaurus build) has not been run locally —ascii-guard==2.3.0would not install in this environment andnpm ciwas out of scope. The one ASCII diagram is inside a fenced code block (excluded by that linter), and the page was scanned for MDX-unsafe{}/<tag>outside code spans. Let CI confirm.🤖 Generated with Claude Code