Skip to content

fix: handle UserCustomSent in history and LLM message builder#188

Open
TajPelc wants to merge 1 commit intocartesia-ai:mainfrom
Dual-Storm:fix/handle-user-custom-sent-in-history
Open

fix: handle UserCustomSent in history and LLM message builder#188
TajPelc wants to merge 1 commit intocartesia-ai:mainfrom
Dual-Storm:fix/handle-user-custom-sent-in-history

Conversation

@TajPelc
Copy link
Copy Markdown

@TajPelc TajPelc commented Apr 1, 2026

Problem

When UserCustomSent is included in run_filter to enable client-to-agent custom events (e.g. billing signals, purchase notifications), the agent crashes on the next user turn with:

ValueError: Unknown event type in history: UserCustomSent

Root Cause

The ConversationRunner records ALL input events into its raw history, including UserCustomSent. When the next UserTurnEnded arrives, the framework passes this history to LlmAgent via event.history. Three code paths don't handle UserCustomSent:

  1. _to_history_event() in history.py — no branch, falls through to raise ValueError
  2. _HISTORY_EVENT_TYPES tuple — missing UserCustomSent, so _validate_history() and _validate_context() reject it with TypeError
  3. _build_messages() in llm_agent.py — no serialization for the type

Reproduction

from line.events import CallStarted, UserTurnEnded, UserCustomSent, CallEnded, UserTurnStarted

async def get_agent(env, call_request):
    agent = LlmAgent(...)
    run_filter = [CallStarted, UserTurnEnded, UserCustomSent, CallEnded]
    cancel_filter = [UserTurnStarted]
    return (agent, run_filter, cancel_filter)
  1. Send a custom event from the client via WebSocket
  2. Handle it in agent.process() (works fine)
  3. User speaks on next turn → LlmAgent.process() crashes

Fix

  • Add UserCustomSent to the InputEvent pass-through block in _to_history_event()
  • Add UserCustomSent to _HISTORY_EVENT_TYPES so validation passes
  • Add serialization in _build_messages() that converts UserCustomSent to a system message with the event metadata as JSON
  • Add tests for UserCustomSent in _build_full_history

With this fix, the LLM naturally sees context like [Client event: {"kind": "diamonds_purchased"}] in its conversation history.

Current Workaround

Without this fix, agents must strip UserCustomSent from event.history before delegating to LlmAgent.process():

async def process(self, env, event):
    if isinstance(event, UserCustomSent):
        yield AgentSendText(text="Handling custom event...")
        return

    # Strip UserCustomSent from history to prevent crash
    if hasattr(event, 'history') and event.history:
        clean = [e for e in event.history if not isinstance(e, UserCustomSent)]
        event = event.model_copy(update={"history": clean})

    async for output in self.inner.process(env, event):
        yield output

This prevents the crash but loses the custom event context from history.


Note

Medium Risk
Changes history validation/normalization and message-building to include UserCustomSent, which can affect prompt content and downstream LLM behavior; risk is moderate but scoped and covered by new tests.

Overview
Prevents crashes when UserCustomSent appears in event.history by allowing it through history validation and _to_history_event() conversion.

Extends _build_messages() to serialize UserCustomSent into a system message containing the event metadata (JSON or a fallback marker), so client-side custom signals become part of the LLM context.

Adds unit tests covering UserCustomSent passthrough in _build_full_history and its serialization behavior in _build_messages().

Written by Cursor Bugbot for commit 720fb73. This will update automatically on new commits. Configure here.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread line/llm_agent/history.py
@TajPelc TajPelc force-pushed the fix/handle-user-custom-sent-in-history branch from 400e829 to 3abb73d Compare April 1, 2026 09:23
## Problem

When UserCustomSent is included in run_filter to enable client-to-agent
custom events (e.g. billing signals, purchase notifications), the agent
crashes on the NEXT user turn with:

  ValueError: Unknown event type in history: UserCustomSent

## Root Cause

The ConversationRunner records ALL input events into its raw history,
including UserCustomSent. When the next UserTurnEnded arrives, the
framework passes this history to LlmAgent via event.history. Three code
paths don't handle UserCustomSent:

1. _to_history_event() in history.py — no branch, raises ValueError
2. _HISTORY_EVENT_TYPES tuple — missing UserCustomSent, so
   _validate_history() and _validate_context() reject it with TypeError
3. _build_messages() in llm_agent.py — no serialization for the type

## Our Workaround (without SDK fix)

In our agent wrapper, we strip UserCustomSent from event.history before
delegating to inner.process():

  if hasattr(event, 'history') and event.history:
      clean = [e for e in event.history if not isinstance(e, UserCustomSent)]
      event = event.model_copy(update={"history": clean})
  async for output in self.inner.process(env, event):
      yield output

This prevents the crash but loses the custom event context from history,
meaning the LLM has no memory of client-side events in subsequent turns.

## SDK Fix

- Add UserCustomSent to the InputEvent pass-through block in
  _to_history_event() so it's preserved in history
- Add UserCustomSent to _HISTORY_EVENT_TYPES so validation passes
- Add serialization in _build_messages() that converts UserCustomSent to
  a system message with the event metadata as JSON, giving the LLM
  useful context about client-side events across turns

With this fix, our workaround becomes unnecessary and the LLM naturally
sees context like [Client event: {"kind": "diamonds_purchased"}] in its
conversation history.
@TajPelc TajPelc force-pushed the fix/handle-user-custom-sent-in-history branch from 3abb73d to 720fb73 Compare April 1, 2026 09:38
@akavi
Copy link
Copy Markdown
Collaborator

akavi commented Apr 1, 2026

Hey @TajPelc, thanks for taking the time to fix this bug!

While the crash on UserCustomSent (issues 1 and 2) are definitely a bug (apologies for not catching it ourselves!), omitting it from the messages sent to the Llm (issue 3) is intentional, with the goal of allowing the developer control over if/how the Llm sees such custom information.

If you do wish to send a custom event to the LLM, we recommend you use the LlmAgent#history API to do so. For this specific case, I'd recommend

async def process(self, env, event):
    if isinstance(event, UserCustomSent):
         meta = event.metadata or {}
        content = json.dumps(meta) if meta else "[custom event]"
        self.inner.add_entry(role="system", content=f"[Client event: {event.content}]")
        
    async for output in self.inner.process(env, event):
        yield output

I realize this is slightly inelegant, and we hope to have a better long-term solution soon.

We've fixed issues 1 and 2 in a separate PR, and will publish the changes shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants