[PROTOTYPE] Letta-backed turn delegation (slice 0) — do not merge#96
Closed
juniperbevensee wants to merge 1 commit into
Closed
[PROTOTYPE] Letta-backed turn delegation (slice 0) — do not merge#96juniperbevensee wants to merge 1 commit into
juniperbevensee wants to merge 1 commit into
Conversation
A Hermes gateway with LETTA_BRAIN_URL + LETTA_BRAIN_AGENT_ID (or
gateway.letta_brain.{base_url,agent_id} in config.yaml) forwards each
turn to a bound Letta agent over REST instead of running the native
AIAgent loop. Surfaces, pairing/group approval, budget, and the reply
path are untouched — the delegation slots into the existing
remote-brain seam in _run_agent_inner, right beside proxy mode.
Throwaway prototype for Swarm Map v1 spec §2B/§3 slice 0 — exists to
answer 'what shape does the Hermes-front proxy want to be?'
Verdict: a config-driven mode at the proxy seam. Do not merge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 21, 2026
juniperbevensee
added a commit
that referenced
this pull request
Jul 22, 2026
Productionizes the #96 prototype's door-context gap (B1.5, ratified: sender- tagged message text). A shared Letta brain sees only the message text over REST, so it can't tell who is speaking or which group — multiplayer is broken. We prepend a compact one-line tag; Letta's server-side history then accumulates identity turn over turn. Current-turn sender + group label only, NOT transcripts. - letta_brain.py: build_sender_tag()/apply_sender_tag() + send_message() gains sender/group params. Documents the no-trailing-slash /messages requirement. - run.py: _run_agent_via_letta derives sender (user_name) + group (chat_name, else type:id; None for DMs) from the SessionSource and passes them through. Validated live 2026-07-21 (self-hosted Letta on Primo): given '[from Alice in #family] ...' the agent replied 'Alice is messaging me from the group #family.' — the inline tag conveys identity, no memory-sync needed. 12/12 tests (8 new: tag construction, send forwarding, DM vs group vs chat_name dispatch). ruff clean. Draft — real B1 also needs SSE streaming + per-agent serialization (spec B3); this lands the door-context bridge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juniperbevensee
added a commit
that referenced
this pull request
Jul 22, 2026
… tool-only turns Swarm Map v1 slice 4: the slice-0 prototype (#96) graduates to the real Hermes-front bridge, per the spec verdict ("real B1 = productionize the prototype + reuse proxy mode's streaming consumer"): - B3 per-agent serialization: turns queue on a per-(server|agent) asyncio lock — Letta processes an agent's messages sequentially, so concurrent group messages must never overlap in flight. Staleness is re-checked after the wait so superseded turns are dropped, not sent. - Streaming: letta_brain.stream_message consumes Letta's SSE endpoint (/messages/stream, stream_tokens) and feeds the same GatewayStreamConsumer proxy mode uses — the setup block is extracted verbatim into _setup_platform_stream_consumer, shared by both remote- brain modes. Defensive parser (parse_stream_line) skips unknown shapes. - Double-turn safety: fallback from a failed stream to the live-validated blocking client happens ONLY when the failure is provably pre-delivery (LettaBrainError.retryable: aiohttp missing, connect-refused, 404/405). Ambiguous failures surface as errors — Letta owns server-side history, and a blind retry would process the turn twice. Partial text that already reached the platform is delivered, never retried. - Auth: LETTA_BRAIN_API_KEY / gateway.letta_brain.api_key → Bearer header (Letta Cloud / secured servers). - Tool-only turns: a turn ending without assistant_message is a quiet empty reply + audit log (message_types), not a user-facing error — resolves the prototype's TODO. 21/21 letta tests (9 new), 170 green across letta + proxy + stream- consumer + platform-streaming suites; ruff clean. The 9 aiohttp-less proxy failures pre-exist on main (env without the optional extra). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Throwaway prototype — do not merge. Swarm Map v1 spec §2B / §3 slice 0: built to answer ONE question empirically — what shape does the Hermes-front proxy (B1) want to be: plugin, config-driven mode, or turn-loop fork?
Answer: config-driven mode, at the existing remote-brain seam
_run_agent_inner(gateway/run.py) already begins with a delegation check: proxy mode (GATEWAY_PROXY_URL→_run_agent_via_proxy) hands the whole turn to a remote OpenAI-compatible server while keeping platform I/O, auth/group-approval, session persistence, and the reply path local. That is exactly the Hermes-front shape, already tested (tests/gateway/test_proxy_mode.py). This prototype adds a sibling check:LETTA_BRAIN_URL+LETTA_BRAIN_AGENT_ID(orgateway.letta_brain.{base_url,agent_id}in config.yaml) routes the turn to_run_agent_via_lettainstead.Rejected shapes:
pre_gateway_dispatch) — fires before auth/pairing (defeats policy inheritance, the whole point of Hermes-front), hooks are sync, and askipaction would force the plugin to re-implement reply delivery/session persistence.agent/conversation_loop.py, 5k lines) — drags in tool-loop machinery Letta doesn't need; the delegation happens two layers above it anyway.What's here
gateway/letta_brain.py— stdlib-urllib client:send_message(base_url, agent_id, text) -> str, extractsassistant_messagecontent,LettaBrainErrorwith user-displayable messages. Endpoints per the validated spike (POST /v1/agents/{id}/messages).gateway/run.py—_get_letta_brain()(mirrors_get_proxy_urlconvention),_run_agent_via_letta()(typing indicator,asyncio.to_thread, proxy-shaped result dict, staleness guard), dispatch check at the top of_run_agent_inner.tests/gateway/test_letta_brain.py— 4 tests (client parse, unreachable error, end-to-end routing with faked HTTP, failure path). All pass; ruff + ty clean; neighboring proxy/dispatch tests still pass (27/27).Known gaps (deliberate — slice 4 work)
context_prompt(channel context, sender identity, group metadata) is dropped on the floor. Slice 4 must decide how door-context reaches the brain (system-message prefix? memory block sync?).🤖 Generated with Claude Code