LemonRouter

View Source

lemon_router owns request normalization, conversation identity, queue semantics, and semantic output tracking. It sits between channel transports and lemon_gateway.

Current Flow

Channel transport or gateway-native ingress
  -> LemonRouter.Router.handle_inbound/1
  -> LemonRouter.RunOrchestrator
  -> LemonRouter.SessionCoordinator
  -> LemonRouter.RunProcess
  -> configured LemonCore.EngineRuntime.submit_execution/1
  -> gateway runtime scheduler / thread worker / run
  -> LemonCore.Bus run events
  -> LemonRouter semantic coalescers / output tracking
  -> LemonCore.DeliveryIntent
  -> LemonChannels.Dispatcher
  -> channel-specific renderer / outbox

Ownership

  • Router owns:
    • RunRequest normalization
    • policy, model, and engine resolution
    • resume resolution and conversation-key selection
    • queue semantics: collect, followup, steer, steer_backlog, interrupt
    • pending-compaction prompt rewriting
    • semantic stream and tool-status coalescing
  • Router does not own:
    • Telegram or Discord rendering details
    • OutboundPayload construction
    • Telegram message-id presentation state
    • gateway slot scheduling or engine lifecycle

Key Modules

ModuleResponsibility
LemonRouter.RouterMain inbound entrypoint, session-key resolution, pending-compaction application, control-plane abort/keepalive hooks
LemonRouter.RunOrchestrator (internal)Builds router-owned submissions from LemonCore.RunRequest and hands them to SessionCoordinator
LemonRouter.SessionCoordinatorSingle owner of per-conversation queue semantics and active-run handoff
Router internal session read modelInternal read model over coordinator-owned active session state
LemonRouter.ConversationKeyCanonical conversation-key selection from structured resume or session key
LemonRouter.ResumeResolverStructured resume resolution before runtime submission
LemonRouter.RunProcessActive-run lifecycle wrapper around one execution
LemonRouter.MediaJobRecorderRecords generated final-answer files into redacted media job metadata before channel delivery
LemonRouter.ChannelsDelivery (internal)Narrow bridge from router-adjacent automation delivery requests into LemonChannels; must not construct OutboundPayload or own channel rendering
LemonRouter.StreamCoalescerSemantic answer coalescing that emits DeliveryIntent snapshots/finalization
LemonRouter.ToolStatusCoalescerSemantic tool-status coalescing that emits DeliveryIntent snapshots/finalization
LemonRouter.PendingCompactionStoreRouter-owned typed wrapper for pending-compaction markers
LemonRouter.AgentEndpointStoreRouter-owned typed wrapper for persistent endpoint aliases
LemonRouter.AgentInboxBEAM-local send API with selectors, fanout, and queue-mode selection
LemonRouter.AgentDirectoryActive/durable session discovery
LemonRouter.AgentEndpointsPersistent route aliases

SurfaceManager.finalize_answer/3 dispatches :stream_finalize first for runs that already streamed answer deltas, then finalizes StreamCoalescer state so late flushes cannot overwrite the final. For non-streamed completions it still finalizes through StreamCoalescer, with a direct :final_text fallback if the coalescer finalize call exits or times out. Completion-time artifact metadata enrichment is best-effort and must not block the final answer path. RunProcess.ArtifactTracker also supports Hermes-style final-answer media directives: a line containing MEDIA:<project-relative-path> is converted into an explicit auto_send_files entry after existing-file, cwd, and symlink escape checks pass. The directive line is removed from the final text before channel rendering, and Telegram/Discord delivery still goes through the normal attachment policy.

Important Contracts

Session And Queue Semantics

SessionCoordinator serializes by conversation key:

  • {:resume, engine, token} when a structured resume token is available
  • {:session, session_key} otherwise

Queue-mode behavior lives here:

  • :collect appends
  • :followup debounces/merges recent followups, except async task/delegated followups which stay separate
  • :steer attempts in-run steer and falls back to followup
  • :steer_backlog attempts in-run steer and falls back to collect
  • active async task/delegated auto-followups are promoted to :steer so completions try to reach the live parent run before falling back to a queued followup
  • :interrupt cancels the active run and inserts the new request at the front

Output Semantics

Router coalescers only track semantic state:

  • accumulated text
  • sequence numbers
  • semantic tool/action state
  • run/session metadata needed for DeliveryIntent

Channels decides:

  • send vs edit
  • truncation
  • reply markup
  • media batching
  • Telegram resume indexing by platform message id

Testing

Run the app suite from the umbrella root:

mix test apps/lemon_router

Useful focused suites during refactors:

mix test apps/lemon_router/test/lemon_router/router_test.exs
mix test apps/lemon_router/test/lemon_router/run_orchestrator_test.exs
mix test apps/lemon_router/test/lemon_router/session_coordinator_test.exs
mix test apps/lemon_router/test/lemon_router/run_process_test.exs
mix test apps/lemon_router/test/lemon_router/stream_coalescer_test.exs
mix test apps/lemon_router/test/lemon_router/tool_status_coalescer_test.exs

Run architecture checks after boundary changes:

mix lemon.quality