Multi-Turn Conversations¶
Multi-turn conversations allow agents to maintain context across multiple interactions, request additional input, and build stateful experiences.
Using Sessions¶
Sessions maintain history and state:
server.agent("chat") do |context|
# Access conversation history
history = context.history
# Build context from history
all_messages = history + context.input
# Generate response considering full context
response = generate_response(all_messages)
SimpleAcp::Models::Message.agent(response)
end
Session History¶
History automatically accumulates:
# First interaction
# history: []
# input: [user: "Hello"]
# After: history becomes [user: "Hello", agent: "Hi!"]
# Second interaction
# history: [user: "Hello", agent: "Hi!"]
# input: [user: "How are you?"]
# After: history becomes [user: "Hello", agent: "Hi!", user: "How are you?", agent: "I'm good!"]
Session State¶
Custom state persists across turns:
server.agent("counter") do |context|
count = context.state || 0
count += 1
context.set_state(count)
SimpleAcp::Models::Message.agent("Turn #{count}")
end
Complex State¶
server.agent("form") do |context|
state = context.state || { step: 1, data: {} }
case state[:step]
when 1
context.set_state(state.merge(step: 2))
SimpleAcp::Models::Message.agent("Enter your name:")
when 2
state[:data][:name] = context.input.first&.text_content
context.set_state(state.merge(step: 3))
SimpleAcp::Models::Message.agent("Enter your email:")
when 3
state[:data][:email] = context.input.first&.text_content
context.set_state(state.merge(step: :done))
SimpleAcp::Models::Message.agent(
"Thanks #{state[:data][:name]}! We'll contact you at #{state[:data][:email]}"
)
else
SimpleAcp::Models::Message.agent("Form complete!")
end
end
Awaiting Input¶
For synchronous input requests within a single run:
server.agent("questioner") do |context|
Enumerator.new do |yielder|
# Ask first question
result = context.await_message(
SimpleAcp::Models::Message.agent("What is your name?")
)
yielder << result
# After resume, get the answer
name = context.resume_message&.text_content
# Ask second question
result = context.await_message(
SimpleAcp::Models::Message.agent("What is your favorite color, #{name}?")
)
yielder << result
color = context.resume_message&.text_content
# Final response
yielder << SimpleAcp::Server::RunYield.new(
SimpleAcp::Models::Message.agent("Great choice, #{name}! #{color} is nice.")
)
end
end
Await Flow¶
sequenceDiagram
participant Client
participant Server
participant Agent
Client->>Server: run_sync (input)
Server->>Agent: execute
Agent-->>Server: await_message("Name?")
Server-->>Client: Run (awaiting)
Client->>Server: run_resume (name)
Server->>Agent: continue with resume
Agent-->>Server: await_message("Color?")
Server-->>Client: Run (awaiting)
Client->>Server: run_resume (color)
Server->>Agent: continue with resume
Agent-->>Server: final response
Server-->>Client: Run (completed)
Client-Side Await Handling¶
run = client.run_sync(agent: "questioner", input: [...])
while run.awaiting?
puts run.await_request.message.text_content
answer = gets.chomp
run = client.run_resume_sync(
run_id: run.run_id,
await_resume: SimpleAcp::Models::MessageAwaitResume.new(
message: SimpleAcp::Models::Message.user(answer)
)
)
end
puts "Final: #{run.output.last.text_content}"
Conversation Patterns¶
Contextual Responses¶
server.agent("assistant") do |context|
# Build full conversation
conversation = context.history.map do |msg|
{ role: msg.role, content: msg.text_content }
end
conversation << {
role: "user",
content: context.input.first&.text_content
}
# Use LLM with full context
response = llm.chat(conversation)
SimpleAcp::Models::Message.agent(response)
end
Memory Management¶
Limit history for efficiency:
server.agent("bounded-chat") do |context|
# Only use last 10 messages
recent_history = context.history.last(10)
conversation = recent_history + context.input
response = generate_response(conversation)
SimpleAcp::Models::Message.agent(response)
end
Summarization¶
server.agent("summarizing-chat") do |context|
state = context.state || { summary: nil }
if context.history.length > 20
# Summarize older history
old_messages = context.history[0..-11]
state[:summary] = summarize(old_messages)
context.set_state(state)
end
# Use summary + recent history
conversation_context = [
state[:summary] ? "Previous context: #{state[:summary]}" : nil,
*context.history.last(10).map(&:text_content),
context.input.first&.text_content
].compact
response = generate_response(conversation_context)
SimpleAcp::Models::Message.agent(response)
end
Conversation Reset¶
server.agent("resettable") do |context|
command = context.input.first&.text_content
if command&.downcase == "reset"
context.set_state(nil)
return SimpleAcp::Models::Message.agent("Conversation reset!")
end
# Normal processing...
end
Interactive Workflows¶
Quiz Game¶
server.agent("quiz") do |context|
state = context.state || {
score: 0,
question_index: 0,
questions: load_questions
}
if state[:question_index] > 0
# Check previous answer
answer = context.input.first&.text_content
correct = state[:questions][state[:question_index] - 1][:answer]
if answer&.downcase == correct.downcase
state[:score] += 1
end
end
if state[:question_index] >= state[:questions].length
context.set_state(nil)
return SimpleAcp::Models::Message.agent(
"Quiz complete! Score: #{state[:score]}/#{state[:questions].length}"
)
end
question = state[:questions][state[:question_index]]
state[:question_index] += 1
context.set_state(state)
SimpleAcp::Models::Message.agent(question[:text])
end
Shopping Assistant¶
server.agent("shop") do |context|
cart = context.state || { items: [], total: 0.0 }
command = context.input.first&.text_content&.downcase
response = case command
when /^add (.+)/
item = find_product($1)
if item
cart[:items] << item
cart[:total] += item[:price]
"Added #{item[:name]} ($#{item[:price]})"
else
"Product not found"
end
when /^remove (.+)/
item = cart[:items].find { |i| i[:name].downcase.include?($1) }
if item
cart[:items].delete(item)
cart[:total] -= item[:price]
"Removed #{item[:name]}"
else
"Item not in cart"
end
when "cart"
items_list = cart[:items].map { |i| "- #{i[:name]}: $#{i[:price]}" }.join("\n")
"Cart:\n#{items_list}\nTotal: $#{cart[:total]}"
when "checkout"
total = cart[:total]
context.set_state(nil)
"Order placed! Total: $#{total}"
else
"Commands: add <item>, remove <item>, cart, checkout"
end
context.set_state(cart)
SimpleAcp::Models::Message.agent(response)
end
Best Practices¶
- Limit history size - Don't let history grow unbounded
- Use state wisely - Store minimal necessary data
- Handle resets - Allow users to start fresh
- Validate state - Check for expected structure
- Test edge cases - Empty history, missing state, etc.
Next Steps¶
- Review Sessions concept
- See Client Sessions for client-side handling
- Explore HTTP Endpoints for session APIs