A Python implementation of a continuous ReAct (Reasoning and Acting) agent for large language models. This project showcases and tests the ContReAct framework.
Unlike traditional ReAct agents that complete a task and stop, this agent keeps going---accumulating knowledge, exploring topics, and building persistent memory over time.
Two execution modes: segmented runs in discrete cycles with reflection and planning between each; unsegmented runs continuously with forced tool calls. Both modes persist state, so you can stop anytime and resume later.
# Install UV package manager (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone and install
git clone https://github.com/szeider/contreact.git
cd contreact
uv sync
# Set up API keys
cp .env.example .env
# Edit .env with your keysYou need:
- OpenRouter API key (for LLM access)
- Tavily API key (optional, for web search tools)
# Copy the quickstart example
cp -r examples/quickstart my_run
# Start the agent
uv run contreact my_run
# The agent will think through the task and send you messages
# Press Ctrl+C to stop anytime (state is saved to checkpoint)
# Resume later - continues exactly where it left off
uv run contreact my_runSegmented mode runs in cycles:
- Reflect --- Review previous cycle, check what's in memory
- Plan --- Decide what to explore next
- Execute --- Search web, write to memory
- Repeat
Between cycles, the agent sends itself a message containing its plan for the next iteration. This self-feedback keeps it on track without external direction.
Unsegmented mode forces tool calls continuously without natural pauses. The agent must call a tool on every turn.
Both modes persist state. Stop anytime; it resumes where it left off.
Each run folder needs two files:
my_run/
├── config.json # Model, tools, execution settings
└── instance_prompt.md # Task instructions
Optional: Add compaction_prompt.md if you enable history compaction for long-running sessions.
See examples/quickstart/ for a minimal setup, or examples/carbon_capture/ for a full-featured example with memory and web search.
{
"model": {
"name": "anthropic/claude-sonnet-4.5",
"temperature": 0.0
},
"tools": ["think", "send_message", "memory_write", "memory_read",
"memory_list", "memory_search", "web_search"],
"execution_mode": "segmented",
"max_tool_calls": 100
}Unsegmented (default): Agent runs continuously with forced tool calls. Simple and direct.
Segmented: Agent completes a cycle, pauses, receives a wake message, starts next cycle. Provides natural checkpoints for monitoring long-running research tasks.
| Tool | Description |
|---|---|
think |
Internal reasoning, logged but not sent anywhere |
send_message |
Send message to operator, wait for response |
stop |
Terminate the agent |
| Tool | Description |
|---|---|
memory_write |
Store or update information with a key (upsert) |
memory_read |
Retrieve stored information |
memory_list |
List all memory keys |
memory_search |
Search memories by content |
memory_update |
Alias for memory_write (both create or update) |
memory_delete |
Remove a memory |
Both memory_write and memory_update behave identically---they create a new memory or update an existing one.
| Tool | Description |
|---|---|
web_search |
Search the web via Tavily |
extract_content |
Extract text from a URL (often fails due to site restrictions) |
Note: extract_content may fail on many sites due to JavaScript rendering, paywalls, or anti-bot measures. The agent should prefer using web_search results directly when possible.
| Tool | Description |
|---|---|
canvas_draw |
Draw shapes on a named canvas (JSON operations) |
canvas_view |
Render canvas to PNG and view it |
canvas_read |
Get raw operations from a canvas |
canvas_list |
List all canvases |
canvas_clear |
Clear a canvas |
canvas_delete |
Delete a canvas |
After the agent has collected data, ask questions:
# Agent answers from memory or does additional research
uv run contreact my_run -q "What is the cost per ton for DAC?"
# Read-only query (no tool access, doesn't modify state)
uv run query my_run "Summarize what you've learned"How -q works: The query is wrapped with instructions telling the agent to use its memory and tools, then deliver the answer via send_message. After answering, the agent continues running (use Ctrl+C to stop). The query becomes part of the session history.
How query works: Sends your question directly to the LLM with the full conversation history but no tool access. Read-only---doesn't modify state. Good for summaries and analysis.
src/contreact/
├── main.py # Entry point
├── graph.py # LangGraph state machine
├── compaction.py # History summarization
├── memory/ # Memory storage + similarity
└── tools/ # Tool implementations
- Thread ID is the folder name: The checkpoint database uses the run folder name as thread ID. If you rename or move the folder, resumption will start a new session instead of continuing the old one.
- Minimal terminal output: The agent prints abbreviated status messages. For detailed history, see
history.jsonlin the run folder. - Token usage: Usage metadata (input/output tokens) is logged to
history.jsonlbut not displayed in the terminal. Parse the log file to calculate costs.
Apache 2.0