LemonAgent. Loop
(lemon_agent v0.1.0)
View Source
Stateless agent loop functions for conversation management.
This module implements the core agent loop logic that orchestrates:
- Streaming LLM responses
- Tool call execution
- Steering message injection
- Follow-up message handling
The loop works with LemonAgent.EventStream to emit events for UI clients
and returns the final list of new messages created during the run.
Entry Points
agent_loop/5- Start a new agent loop with prompt messagesagent_loop_continue/4- Continue from an existing context
Event Flow
The loop emits events in a specific sequence:
{:agent_start}
{:tool_schema_snapshot, snapshot}
{:turn_start}
{:loop_state_transition, nil, :initializing, %{phase: :run_start}}
{:message_start, prompt}
{:message_end, prompt}
{:message_start, assistant_msg}
{:message_update, assistant_msg, event}
...
{:message_end, assistant_msg}
{:tool_execution_start, id, name, args}
{:tool_execution_update, id, name, args, partial}
{:tool_execution_end, id, name, result, is_error}
{:message_start, tool_result}
{:message_end, tool_result}
{:turn_end, assistant_msg, tool_results}
{:loop_budget_exhausted, details}
... (more turns if tool calls or steering)
{:agent_end, new_messages}Abort Handling
The signal parameter is a reference that can be used for abort handling.
Check for abort by monitoring the signal or using Process messages.
Summary
Functions
Start an agent loop with new prompt messages.
Continue an agent loop from an existing context without adding new messages.
Start an agent loop and return an Enumerable of events.
Continue an agent loop and return an Enumerable of events.
Functions
@spec agent_loop( [LemonAgent.Types.agent_message()], LemonAgent.Types.AgentContext.t(), LemonAgent.Types.AgentLoopConfig.t(), reference() | nil, LemonAgent.Types.AgentLoopConfig.stream_fn() | nil ) :: LemonAgent.EventStream.t()
Start an agent loop with new prompt messages.
Creates a new EventStream, emits lifecycle events for the prompts,
and starts the main loop in an async task.
Parameters
prompts- List of agent messages to add to context as the promptcontext- The current agent context (system prompt, messages, tools)config- Agent loop configuration (model, convert_to_llm, etc.)signal- Optional abort signal reference for cancellationstream_fn- Optional custom stream function (defaults toLemonAi.stream/3)
Returns
An EventStream that will emit agent events and complete with
{:ok, new_messages} where new_messages contains only the messages
created during this run (not the original context messages).
Examples
context = AgentContext.new(system_prompt: "You are helpful")
config = %AgentLoopConfig{model: model, convert_to_llm: &convert/1}
prompt = %LemonAi.Types.UserMessage{content: "Hello", timestamp: now()}
stream = LemonAgent.Loop.agent_loop([prompt], context, config, nil, nil)
for event <- EventStream.events(stream) do
IO.inspect(event)
end
@spec agent_loop( [LemonAgent.Types.agent_message()], LemonAgent.Types.AgentContext.t(), LemonAgent.Types.AgentLoopConfig.t(), reference() | nil, LemonAgent.Types.AgentLoopConfig.stream_fn() | nil, pid() | nil ) :: LemonAgent.EventStream.t()
@spec agent_loop_continue( LemonAgent.Types.AgentContext.t(), LemonAgent.Types.AgentLoopConfig.t(), reference() | nil, LemonAgent.Types.AgentLoopConfig.stream_fn() | nil ) :: LemonAgent.EventStream.t()
Continue an agent loop from an existing context without adding new messages.
Used for retries or continuing after tool results have been added to context. The context must not be empty and the last message must not be an assistant message.
Parameters
context- The agent context to continue fromconfig- Agent loop configurationsignal- Optional abort signal referencestream_fn- Optional custom stream function
Raises
ArgumentErrorif context has no messagesArgumentErrorif last message is an assistant message
Examples
# After adding tool results to context
stream = LemonAgent.Loop.agent_loop_continue(context, config, nil, nil)
@spec agent_loop_continue( LemonAgent.Types.AgentContext.t(), LemonAgent.Types.AgentLoopConfig.t(), reference() | nil, LemonAgent.Types.AgentLoopConfig.stream_fn() | nil, pid() | nil ) :: LemonAgent.EventStream.t()
@spec stream( [LemonAgent.Types.agent_message()], LemonAgent.Types.AgentContext.t(), LemonAgent.Types.AgentLoopConfig.t(), LemonAgent.Types.AgentLoopConfig.stream_fn() | nil ) :: Enumerable.t()
Start an agent loop and return an Enumerable of events.
This is a convenience wrapper around agent_loop/5 that returns the
EventStream's events directly as an Enumerable, suitable for use with
Enum or Stream functions.
Parameters
prompts- List of agent messages to add to context as the promptcontext- The current agent contextconfig- Agent loop configurationstream_fn- Optional custom stream function
Returns
An Enumerable of agent events.
@spec stream_continue( LemonAgent.Types.AgentContext.t(), LemonAgent.Types.AgentLoopConfig.t(), LemonAgent.Types.AgentLoopConfig.stream_fn() | nil ) :: Enumerable.t()
Continue an agent loop and return an Enumerable of events.
This is a convenience wrapper around agent_loop_continue/4 that returns
the EventStream's events directly as an Enumerable.
Parameters
context- The agent context to continue fromconfig- Agent loop configurationstream_fn- Optional custom stream function
Returns
An Enumerable of agent events.