A framework for modeling agent thoughts and conversations, enabling more natural and human-like interactions between multiple AI agents and humans.
- Overview
- Research Background
- Installation
- Project Structure
- Distribution
- Key Components
- Usage Examples
- License
- Citation
- Contact
Thoughtful Agents provides a structured approach to modeling the internal thought processes of AI agents during conversations. Rather than simply predicting conversational turns, this framework enables proactive AI driven by its own internal "thoughts".
This framework is based on the paper Proactive Conversational Agents with Inner Thoughts, published at CHI 2025.
Inspired by cognitive architectures and LLM prompting techniques, the framework comprises five stages:
- Trigger - Initiating the thought process
- Retrieval - Accessing relevant memories and context
- Thought Formation - Generating potential thoughts
- Evaluation - Assessing intrinsic motivation to express thoughts
- Participation - Deciding when and how to engage in conversation
The Python implementation includes:
- Thinking engine for thought generation, evaluation, selection, and articulation
- System 1 (fast, automatic) and System 2 (slow, deliberate) thinking
- Mental object management (thoughts, memories)
- Saliency-based memory and thought retrieval
- Conversation and event tracking
- Turn-taking prediction and engine for determining when and who should speak next
- Proactivity configuration for agents
Most current LLM-based systems treat AI as passive respondents, responding only to explicit human prompts. The Inner Thoughts framework takes an alternative approach inspired by human cognition, where we process others' words, reflect on our experiences, and develop an internal train of thoughts before deciding to participate.
This approach models the distinction between covert responses (internal thoughts and feelings) and overt responses (verbal utterances) in human communication. The AI participant determines whether to engage based on an evaluation of its intrinsic motivation to express a particular thought at that moment.
pip install thoughtful-agents- Clone the repository:
git clone https://github.com/xybruceliu/thoughtful-agents.git
cd thoughtful-agents- Install the package and its dependencies:
pip install -e .After installation, download the required spaCy model:
python -m spacy download en_core_web_smOr use the provided script:
python scripts/download_spacy_model.pyexport OPENAI_API_KEY=your_api_key_here# Customize the completion model (default: gpt-4o)
export COMPLETION_MODEL=gpt-4-turbo
# Customize the embedding model (default: text-embedding-3-small)
export EMBEDDING_MODEL=text-embedding-3-largeThe project is organized as follows:
thoughtful_agents/models/: Core model classesparticipant.py: Participant, Human, and Agent classesthought.py: Thought-related classesmemory.py: Memory-related classesconversation.py: Conversation and Event classesmental_object.py: Base class for mental objectsenums.py: Enumeration types
thoughtful_agents/utils/: Utility functionsllm_api.py: OpenAI API interactionsaliency.py: Saliency computationthinking_engine.py: Functions for thought generation, evaluation, and articulationturn_taking_engine.py: Turn-taking predictiontext_splitter.py: Text splitting using spaCy
examples/: Example implementationshello_world.py: Simple example with two agents in conversationai_thought_process.py: Detailed example showing the AI's thought processlecture_practice.py: Example of an AI providing proactive feedback during a lecture practicemultiparty_conversation.py: Example of a multi-party conversation between three AI agents
The framework is available as a PyPI package:
pip install thoughtful-agentsPyPI Package: https://pypi.org/project/thoughtful-agents/
The framework models different types of conversation participants:
Participant: Base class for all conversation participantsHuman: Represents human participants in the conversationAgent: AI agents that can generate thoughts, evaluate them, and decide when to participate
Both Thought and Memory are subclasses of MentalObject.
The framework distinguishes between:
Thought: Temporary mental objects representing current thinkingMemory: Longer-term mental objects stored for future retrieval
They are managed through the ThoughtReservoir and MemoryStore classes respectively.
The Conversation class manages the overall conversation state, while Event objects represent individual utterances or any other actions within the conversation.
The MentalObject class serves as the base for all mental entities in the system. It includes attributes like content, embedding, saliency, and weight that determine how important and relevant the object is in a given context.
Key functions in thinking_engine.py include:
generate_system1_thought(): Creates quick, intuitive thoughts based on immediate contextgenerate_system2_thoughts(): Produces deliberate, reflective thoughts with deeper reasoningevaluate_thought(): Assesses thoughts and assigns intrinsic motivation scores (1-5)articulate_thought(): Transforms internal thoughts into natural language utterances
The turn-taking engine implements Sacks et al.'s conversation analysis principles (Simplest Systematics):
- Turn-allocation: When a speaker directly selects the next speaker (e.g., "What do you think, Alice?")
- Self-selection: When no specific speaker is selected, any participant may take the floor
The framework implements this through two key functions:
predict_turn_taking_type: Analyzes conversations to determine if a specific speaker is selected or if the floor is opendecide_next_speaker_and_utterance: Selects the next speaker based on turn allocation type, intrinsic motivation scores, and proactivity settings
Inner Thoughts offers fine-grained control over AI conversation participation through three proactivity layers:
-
Overt Proactivity: Controls conversation engagement tendency via the
system1_probparameter (0-1). -
Covert Proactivity: Sets motivation threshold for expression using the
im_thresholdparameter (1-5). -
Tonal Proactivity: Adjusts language assertiveness with the
proactive_toneparameter (true/false).
The framework also supports interruption through the interrupt_threshold parameter (1-5), allowing AIs to override turn allocation when highly motivated.
To determine when and how the AI participates:
- For open turns: AI speaks if motivation exceeds threshold
- For allocated turns: AI uses highest-rated thought
- For others' turns: AI interrupts only with sufficient motivation
The hello_world.py example demonstrates a simple conversation between two AI agents:
# Create a conversation with a simple context
conversation = Conversation(context="A friendly chat between Alice and Bob.")
# Create agents with specific proactivity configurations
alice = Agent(name="Alice", proactivity_config={
'im_threshold': 3.2,
'system1_prob': 0.3,
'interrupt_threshold': 4.5
})
bob = Agent(name="Bob", proactivity_config={
'im_threshold': 3.2,
'system1_prob': 0.3,
'interrupt_threshold': 4.5
})
# Add background knowledge to the agents
alice.initialize_memory("I am a software engineer who likes to code.")
bob.initialize_memory("I am a cognitive scientist who works on understanding the human mind.")
# Add agents to the conversation
conversation.add_participant(alice)
conversation.add_participant(bob)
# Alice starts the conversation
await alice.send_message("I'm recently thinking about adopting a cat. What do you think about this?", conversation)
# Predict the next speaker before broadcasting the event.
# This to determine whether the next_turn is turn-allocation or self-selection.
turn_allocation_type = await predict_turn_taking_type(conversation)
# Broadcast the event to let all agents think
await conversation.broadcast_event(new_event)
# Decide the next speaker and their utterance
next_speaker, utterance = await decide_next_speaker_and_utterance(conversation)For optimal agent behavior, follow this sequence of operations for event processing:
- Create an event using
participant.send_message() - Call
predict_turn_taking_type(conversation)to determine the turn allocation type (either a specific speaker name or "anyone") - Call
conversation.broadcast_event(event)to let agents process the event - Use
decide_next_speaker_and_utterance(conversation)to get the actual next speaker
This sequence ensures that when agents process an event during broadcast_event, they have access to the turn allocation prediction (pred_next_turn) needed for proper thought selection.
Note: The framework includes a safety mechanism that automatically calls predict_turn_taking_type if needed during broadcast_event, but it's recommended to call it explicitly for clarity and consistency.
The ai_thought_process.py provides a more detailed look at the AI's internal thought process:
# Create a human and an AI agent
human = Human(name="Human")
ai_agent = Agent(name="AI Assistant", proactivity_config={
'im_threshold': 3.2,
'system1_prob': 0.3,
'interrupt_threshold': 4.5
})
# Human starts the conversation
human_event = await human.send_message("How are AI agents designed to participate in conversations?", conversation)
# AI thinking process
await ai_agent.recalibrate_saliency_for_event(human_event)
ai_agent.add_event_to_memory(human_event)
new_thoughts = await ai_agent.generate_thoughts(conversation, num_system1=1, num_system2=2)
await ai_agent.evaluate_thoughts(new_thoughts, conversation)
# AI selects and articulates thoughts
selected_thoughts = await ai_agent.select_thoughts(new_thoughts, conversation)
if selected_thoughts:
ai_response = await ai_agent.articulate_thought(selected_thoughts[0], conversation)
await ai_agent.send_message(ai_response, conversation)The lecture_practice.py example demonstrates how an AI assistant can provide proactive feedback during a lecture practice, without the user having to ask for it:
# Create a conversation with context for practicing a lecture
conversation = Conversation(context="A user is practicing a lecture on artificial intelligence.")
# Create the human presenter and AI feedback assistant
human = Human(name="Presenter")
ai_assistant = Agent(name="Feedback Assistant", proactivity_config={
'im_threshold': 3.0,
'system1_prob': 0.0,
'interrupt_threshold': 3.5 # Higher threshold to reduce interruptions
})
# Add background knowledge to the AI assistant
background_knowledge = """I'm an AI assistant designed to provide helpful feedback on presentations and lectures.
My goal is to be helpful but not intrusive. I should:
1. Only interrupt for critical feedback that would significantly improve the presentation.
2. Note minor issues but save them for when the presenter pauses or asks for feedback.
3. Pay attention to content accuracy, delivery style, pacing, and engagement."""
ai_assistant.initialize_memory(background_knowledge, by_paragraphs=True)
# Process each lecture segment in a loop
for i, segment in enumerate(lecture_segments):
# Human presenter speaks
human_event = await human.send_message(segment["content"].strip(), conversation)
# Predict the next speaker before broadcasting the event.
# This to determine whether the next_turn is turn-allocation or self-selection.
turn_allocation_type = await predict_turn_taking_type(conversation)
# Broadcast the event to let the AI think
await conversation.broadcast_event(human_event)
# Use the turn-taking engine to decide if AI should provide feedback
next_speaker, utterance = await decide_next_speaker_and_utterance(conversation)
if next_speaker and next_speaker.name == "Feedback Assistant":
await ai_assistant.send_message(utterance, conversation)The multiparty_conversation.py example demonstrates a casual multi-party conversation between three AI agents discussing their weekend activities. Each agent has a different "persona" and proactivity configuration. They will participate in the conversation proactively based on their intrinsic motivation scores, deciding whether to speak or not in each turn.
# Create a conversation with a simple context
conversation = Conversation(context="A casual conversation between friends Alice, Bob, and Charlie about what they did last weekend.")
# Create three agents with their respective proactivity configurations
alice = Agent(name="Alice", proactivity_config={
'im_threshold': 3.0, # Moderate threshold for speaking
'system1_prob': 0.4, # Moderate chance of quick, intuitive responses
'interrupt_threshold': 4.2 # High threshold to avoid frequent interruptions
})
bob = Agent(name="Bob", proactivity_config={
'im_threshold': 2.8, # Slightly more eager to speak
'system1_prob': 0.5, # Higher chance of quick, intuitive responses
'interrupt_threshold': 4.0 # More likely to interrupt when highly motivated
})
charlie = Agent(name="Charlie", proactivity_config={
'im_threshold': 3.0, # Moderate threshold for speaking
'system1_prob': 0.3, # More deliberate responses
'interrupt_threshold': 4.5 # Very unlikely to interrupt
})
# Initialize long-term memories for each agent
alice.initialize_memory(alice_memories, by_paragraphs=True)
bob.initialize_memory(bob_memories, by_paragraphs=True)
charlie.initialize_memory(charlie_memories, by_paragraphs=True)
# Add agents to the conversation
conversation.add_participant(alice)
conversation.add_participant(bob)
conversation.add_participant(charlie)
# Alice starts the conversation
new_event = await alice.send_message("Hey everyone! What did you all do last weekend?", conversation)
# Predict the turn allocation type before broadcasting the event
turn_allocation_type = await predict_turn_taking_type(conversation)
# Broadcast the event to let all agents think
await conversation.broadcast_event(new_event)
# Run the conversation for multiple turns
for turn in range(num_turns):
# Determine the next speaker based on intrinsic motivation
next_speaker, utterance = await decide_next_speaker_and_utterance(conversation)
if next_speaker:
# Send the message
new_event = await next_speaker.send_message(utterance, conversation)
# Predict the turn allocation type before broadcasting the event
turn_allocation_type = await predict_turn_taking_type(conversation)
# Broadcast the event to let all agents think
await conversation.broadcast_event(new_event)This project is licensed under the Apache 2.0 License - see the LICENSE file for details.
If you use this framework in your research, please cite:
@inproceedings{liu2025inner,
title={Proactive Conversational Agents with Inner Thoughts},
author={Liu, Xingyu Bruce and Fang, Shitao and Shi, Weiyan and Wu, Chien-Sheng and Igarashi, Takeo and Chen, Xiang Anthony},
booktitle = {Proceedings of the 2025 CHI Conference on Human Factors in Computing Systems},
year = {2025},
publisher = {Association for Computing Machinery},
address = {New York, NY, USA},
location = {Yokohama, Japan},
series = {CHI '25},
keywords = {Full},
url = {https://doi.org/10.1145/3706598.3713760},
doi = {10.1145/3706598.3713760},
}
For questions or feedback, please feel free to reach out to Xingyu Bruce Liu!
