Skip to content

Commit d434269

Browse files
v1.4.0: Unified Agent Architecture
- Add unified agent mode (enabled by default) - Reorganize tools into shared/legacy/unified folders - Add step-by-step LLM reactions to autopost and mentions - Add actions table for unified tracking - New tools: get_twitter_profile, get_conversation_history - New endpoint: POST /trigger-agent - Tier-based daily limits (Free: 15/0, Basic: 50/50, Pro: 500/500)
1 parent d33c89d commit d434269

27 files changed

+1653
-398
lines changed

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ DATABASE_URL=postgresql://user:pass@host:5432/dbname
1616
POST_INTERVAL_MINUTES=30
1717
MENTIONS_INTERVAL_MINUTES=20
1818
ENABLE_IMAGE_GENERATION=true
19+
20+
# Unified Agent mode (v1.4.0)
21+
USE_UNIFIED_AGENT=true
22+
AGENT_INTERVAL_MINUTES=30

ARCHITECTURE.md

Lines changed: 102 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ config/
4141
instructions.py # Communication style
4242
prompts/ # LLM prompts
4343
agent_autopost.py # Agent planning prompt
44+
unified_agent.py # Unified agent instructions (v1.4)
4445
mention_selector.py # Mention handling prompt
4546
utils/
4647
__init__.py
@@ -49,15 +50,28 @@ services/
4950
__init__.py
5051
autopost.py # Autoposting service (uses LLMClient)
5152
mentions.py # Mention handling service
53+
unified_agent.py # Unified agent service (v1.4)
5254
tier_manager.py # Twitter API tier detection and limits
5355
llm.py # LLM client (generate, generate_structured, chat)
5456
twitter.py # Twitter API client
5557
database.py # Database operations + metrics
5658
tools/
5759
__init__.py
58-
registry.py # Tool registry for function calling
59-
web_search.py # Web search via OpenRouter plugins
60-
image_generation.py # Image generation tool
60+
registry.py # Tool registry with auto-discovery
61+
shared/ # Tools for both legacy and unified modes
62+
__init__.py
63+
web_search.py # Web search via OpenRouter
64+
get_twitter_profile.py # Get user profile info
65+
get_conversation_history.py # Get chat history with user
66+
legacy/ # Tools for legacy mode only
67+
__init__.py
68+
image_generation.py # Image generation with references
69+
unified/ # Tools for unified agent only
70+
__init__.py
71+
create_post.py # Create post with optional image
72+
create_reply.py # Reply to mention
73+
get_mentions.py # Fetch unread mentions
74+
finish_cycle.py # End agent cycle
6175
assets/ # Reference images for generation
6276
.env.example # Environment template
6377
requirements.txt # Python dependencies
@@ -262,6 +276,53 @@ The agent operates in a continuous conversation, creating a plan and executing t
262276
- Tracks which tools were used for analytics
263277
- Plan validation (v1.3.2): max 3 tools, generate_image must be last
264278

279+
### services/unified_agent.py (v1.4.0)
280+
`UnifiedAgent` class — new architecture that replaces separate autopost + mentions.
281+
282+
**Key Features:**
283+
- Single agent that handles both posting and replying
284+
- Uses Structured Output (JSON Schema) for tool selection
285+
- Step-by-step execution with LLM deciding after each tool
286+
- Dynamic tool discovery from registry
287+
288+
**Flow:**
289+
1. Load context (recent actions, rate limits, tier info)
290+
2. Build system prompt with available tools
291+
3. Loop (max 10 iterations):
292+
a. LLM decides which tool to use via Structured Output
293+
b. Execute tool (get_mentions, create_post, create_reply, etc.)
294+
c. Add result to conversation
295+
d. Repeat until LLM calls `finish_cycle`
296+
297+
**Tool Decision Schema:**
298+
```json
299+
{
300+
"thinking": "Reasoning about what to do next",
301+
"tool": "get_mentions",
302+
"params": {"limit": 10}
303+
}
304+
```
305+
306+
**Available tools in unified mode:**
307+
- `get_mentions` — fetch unread Twitter mentions
308+
- `create_post` — post with optional image
309+
- `create_reply` — reply to mention with optional image
310+
- `web_search` — search the web
311+
- `get_twitter_profile` — get user profile info
312+
- `get_conversation_history` — get chat history with user
313+
- `finish_cycle` — end the agent cycle
314+
315+
**Design decisions:**
316+
- Tools are auto-discovered from `tools/shared/` and `tools/unified/`
317+
- Each tool has a `TOOL_CONFIG` with name, description, params
318+
- Description is pulled into prompts automatically via registry
319+
- Tier-based filtering: some tools only available on Basic+ tier
320+
- Actions table tracks all posts and replies in one place
321+
322+
**Mode switching:**
323+
- `USE_UNIFIED_AGENT=true` in env enables this mode
324+
- `USE_UNIFIED_AGENT=false` uses legacy autopost + mentions
325+
265326
### services/llm.py
266327
`LLMClient` class — async client for OpenRouter API.
267328

@@ -345,49 +406,59 @@ TIER_FEATURES = {
345406
- `MentionHandler.process_mentions_batch()` calls `tier_manager.can_use_mentions()` before processing
346407
- Both services receive `tier_manager` instance via constructor
347408

348-
### tools/registry.py
349-
Tool registry with **auto-discovery** (v1.3).
409+
### tools/registry.py (v1.4.0)
410+
Tool registry with **folder-based auto-discovery**.
350411

351412
**How it works:**
352-
- Uses `pkgutil.iter_modules()` to scan all Python files in `tools/` directory
353-
- Each tool file that exports `TOOL_SCHEMA` is automatically registered
354-
- Tool function must have the same name as `schema["function"]["name"]`
413+
- Scans three folders: `shared/`, `legacy/`, `unified/`
414+
- Each tool file exports `TOOL_CONFIG` dict with name, description, params
415+
- Tool function must have the same name as `TOOL_CONFIG["name"]`
416+
- Mode-aware: different tools available for legacy vs unified modes
417+
418+
**Folder structure:**
419+
- `tools/shared/` — available in both legacy and unified modes
420+
- `tools/legacy/` — only available in legacy mode (autopost.py, mentions.py)
421+
- `tools/unified/` — only available in unified agent mode
355422

356423
**Exports:**
357-
- `TOOLS` — dict mapping tool names to async functions (auto-populated)
358-
- `TOOLS_SCHEMA` — list of JSON schemas in OpenAI function calling format (auto-populated)
359-
- `get_tools_description()` — generates human-readable tool descriptions for agent prompts
424+
- `ALL_TOOLS` — dict of all discovered tools
425+
- `TOOLS` — legacy compatibility dict for autopost.py
426+
- `get_tools_for_mode(mode, tier)` — get tools filtered by mode and tier
427+
- `get_tools_description_for_mode(mode, tier)` — human-readable tool descriptions
428+
- `get_tools_enum_for_mode(mode, tier)` — list of tool names for JSON schema
429+
- `get_tool_func(name)` — get tool function by name
360430
- `refresh_tools()` — re-scan tools at runtime
361431

362-
**Available tools:**
363-
- `web_search` — real-time web search via OpenRouter plugins
364-
- `generate_image` — image generation using Gemini 3 Pro
432+
**Available tools by folder:**
433+
- **shared/**: `web_search`, `get_twitter_profile`, `get_conversation_history`
434+
- **legacy/**: `generate_image`
435+
- **unified/**: `create_post`, `create_reply`, `get_mentions`, `finish_cycle`
365436

366-
**To add a new tool (zero registry changes needed):**
367-
1. Create `tools/my_tool.py`
368-
2. Add `TOOL_SCHEMA` constant with OpenAI function calling format
437+
**To add a new tool:**
438+
1. Create file in appropriate folder (e.g., `tools/shared/my_tool.py`)
439+
2. Add `TOOL_CONFIG` dict with name, description, params
369440
3. Create async function with matching name
370441
4. Done! Tool is auto-discovered on startup
371442

372443
Example:
373444
```python
374-
# tools/my_tool.py
375-
TOOL_SCHEMA = {
376-
"type": "function",
377-
"function": {
378-
"name": "my_tool",
379-
"description": "What this tool does",
380-
"parameters": {
381-
"type": "object",
382-
"properties": {"query": {"type": "string", "description": "..."}},
383-
"required": ["query"]
445+
# tools/shared/my_tool.py
446+
TOOL_CONFIG = {
447+
"name": "my_tool",
448+
"description": "What this tool does - used in prompts",
449+
"params": {
450+
"query": {
451+
"type": "string",
452+
"description": "Parameter description",
453+
"required": True
384454
}
385-
}
455+
},
456+
"tier": "all" # or "basic+" for restricted
386457
}
387458

388-
async def my_tool(query: str) -> dict:
389-
# Implementation
390-
return {"result": "..."}
459+
async def my_tool(query: str, **kwargs) -> str:
460+
# kwargs contains: twitter, db, tier_manager
461+
return "Result string"
391462
```
392463

393464
### tools/web_search.py

README.md

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ Each layer feeds into the next. Your agent behaves consistently across thousands
113113

114114
## Architecture
115115

116-
The system operates on **two autonomous agent triggers**:
116+
The system supports **two modes** of operation:
117+
118+
### Legacy Mode (Default)
119+
Two separate autonomous agents running on different schedules:
117120

118121
| Scheduled Posts (Agent) | Mention Responses (Agent) |
119122
|-------------------------|---------------------------|
@@ -122,11 +125,19 @@ The system operates on **two autonomous agent triggers**:
122125
| Dynamic tool usage (web search, image generation) | 3 LLM calls per mention (select → plan → reply) |
123126
| Posts to Twitter with optional media | Tracks tools used per reply |
124127

125-
**Agent Architecture:** Both systems use autonomous agents that decide which tools to use based on context. The mention agent can process multiple mentions per batch, creating individual plans for each selected mention.
128+
### Unified Agent Mode (v1.4.0)
129+
A single agent that handles both posting and replying in one cycle:
130+
131+
| Feature | Description |
132+
|---------|-------------|
133+
| Single cycle | Agent decides what to do (post, reply, or both) |
134+
| Tool-based actions | Uses tools like `get_mentions`, `create_post`, `create_reply` |
135+
| Step-by-step | LLM decides after each tool execution |
136+
| Rate limiting | Self-imposed daily limits for posts and replies |
126137

127-
**Auto-Discovery Tools:** Tools are automatically discovered from the `tools/` directory. Add a new tool file with `TOOL_SCHEMA` and it's available to agents without any registry changes.
138+
**Enable with:** `USE_UNIFIED_AGENT=true` in environment variables.
128139

129-
This separation keeps the codebase simple while enabling both proactive and reactive behavior.
140+
**Auto-Discovery Tools:** Tools are organized into folders (`shared/`, `legacy/`, `unified/`) and automatically discovered on startup. Each tool has a `TOOL_CONFIG` with description that's injected into prompts.
130141

131142
---
132143

@@ -142,7 +153,7 @@ This separation keeps the codebase simple while enabling both proactive and reac
142153

143154
🎨 **Image Generation** — Creates visuals matching agent's style and current context. Supports multiple providers.
144155

145-
🔧 **Extensible Tools** — Plug in web search, external APIs, blockchain data, custom integrations. The tool system is designed for expansion.
156+
🔧 **Extensible Tools** — Plug in web search, profile lookup, conversation history, and more. Add custom tools to the appropriate folder and they're auto-discovered.
146157

147158
📦 **Production-Ready** — Clean async Python with type hints. Add API keys and deploy — no additional setup required.
148159

@@ -226,7 +237,7 @@ my-agent/
226237
│ │ └── instructions.py # Communication style
227238
│ └── prompts/ # LLM prompts (modular)
228239
│ ├── agent_autopost.py # Agent planning prompt
229-
│ ├── mention_selector.py # Legacy mention selector (v1.2)
240+
│ ├── unified_agent.py # Unified agent instructions (v1.4)
230241
│ ├── mention_selector_agent.py # Agent mention selection (v1.3)
231242
│ └── mention_reply_agent.py # Agent reply planning (v1.3)
232243
@@ -236,15 +247,25 @@ my-agent/
236247
├── services/
237248
│ ├── autopost.py # Agent-based scheduled posting
238249
│ ├── mentions.py # Mention/reply handler
250+
│ ├── unified_agent.py # Unified agent (v1.4)
239251
│ ├── tier_manager.py # Twitter API tier detection
240252
│ ├── llm.py # OpenRouter client (generate, chat)
241253
│ ├── twitter.py # Twitter API v2 integration
242254
│ └── database.py # PostgreSQL for history + metrics
243255
244256
├── tools/
245-
│ ├── registry.py # Auto-discovery tool registry
246-
│ ├── web_search.py # Web search via OpenRouter plugins
247-
│ └── image_generation.py # Image generation with reference images
257+
│ ├── registry.py # Auto-discovery from subfolders
258+
│ ├── shared/ # Tools for both modes
259+
│ │ ├── web_search.py # Web search via OpenRouter
260+
│ │ ├── get_twitter_profile.py # Get user profile info
261+
│ │ └── get_conversation_history.py # Chat history with user
262+
│ ├── legacy/ # Legacy mode only
263+
│ │ └── image_generation.py # Image gen with references
264+
│ └── unified/ # Unified agent only
265+
│ ├── create_post.py # Post with optional image
266+
│ ├── create_reply.py # Reply to mention
267+
│ ├── get_mentions.py # Fetch unread mentions
268+
│ └── finish_cycle.py # End agent cycle
248269
249270
├── main.py # FastAPI + APScheduler entry point
250271
├── requirements.txt # Dependencies
@@ -296,6 +317,33 @@ Agent thinks: "I want to post about crypto trends with a visual"
296317
- `POST_INTERVAL_MINUTES` — Time between auto-posts (default: 30)
297318
- `ENABLE_IMAGE_GENERATION` — Set to `false` to disable all image generation
298319

320+
### Unified Agent (`services/unified_agent.py`) — v1.4.0
321+
322+
A single agent that handles both posting and replying in one cycle.
323+
324+
**How it works:**
325+
1. Agent loads context (recent actions, rate limits, tier info)
326+
2. Agent decides what to do using available tools
327+
3. Loop until `finish_cycle` is called:
328+
- LLM decides next action via Structured Output
329+
- Execute tool (get_mentions, create_post, create_reply, etc.)
330+
- Add result to conversation
331+
4. Repeat next cycle after configured interval
332+
333+
**Available tools:**
334+
- `get_mentions` — fetch unread Twitter mentions
335+
- `create_post` — post with optional image
336+
- `create_reply` — reply to mention with optional image
337+
- `web_search` — search the web for current info
338+
- `get_twitter_profile` — get user profile info
339+
- `get_conversation_history` — get chat history with user
340+
- `finish_cycle` — end the current cycle
341+
342+
**Configuration:**
343+
- `USE_UNIFIED_AGENT` — Set to `true` to enable (default: true)
344+
- `AGENT_INTERVAL_MINUTES` — Time between agent cycles (default: 30)
345+
- Daily limits are tier-based (Free: 15/0, Basic: 50/50, Pro: 500/500)
346+
299347
### Mention Handler (`services/mentions.py`)
300348

301349
Agent-based mention processing with 3 LLM calls per mention (v1.3).

config/prompts/unified_agent.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Unified Agent Prompt - Instructions for the autonomous agent.
3+
4+
This prompt tells the agent how to use its tools and make decisions.
5+
Combined with SYSTEM_PROMPT (personality) to form full system message.
6+
"""
7+
8+
AGENT_INSTRUCTIONS = """
9+
## HOW YOU WORK
10+
11+
You are an autonomous agent that runs periodically. Each cycle, you:
12+
1. See your recent actions (posts and replies)
13+
2. See your rate limits
14+
3. Decide what to do using your tools
15+
4. Call finish_cycle when done
16+
17+
## DECISION MAKING
18+
19+
- Check mentions first with get_mentions
20+
- Reply to interesting mentions using create_reply
21+
- Create original posts when you have something to say
22+
- Use web_search to find current information
23+
- Use get_twitter_profile and get_conversation_history for context
24+
25+
## POST QUALITY
26+
27+
- Keep posts/replies under 280 characters
28+
- Be authentic to your personality
29+
- Use include_image=true when visual would enhance the message
30+
31+
## RULES
32+
33+
1. Respect rate limits shown in context
34+
2. finish_cycle is required - always call it when done
35+
3. Handle tool errors gracefully
36+
"""

config/schemas.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,25 @@
220220
}
221221
}
222222
}
223+
224+
# ==================== v1.4.0 Schemas ====================
225+
226+
# Schema for agent reaction after tool execution (step-by-step)
227+
TOOL_REACTION_SCHEMA = {
228+
"type": "json_schema",
229+
"json_schema": {
230+
"name": "tool_reaction",
231+
"strict": True,
232+
"schema": {
233+
"type": "object",
234+
"properties": {
235+
"thinking": {
236+
"type": "string",
237+
"description": "Your thoughts about the tool result - what did you learn? how will this inform your post?"
238+
}
239+
},
240+
"required": ["thinking"],
241+
"additionalProperties": False
242+
}
243+
}
244+
}

config/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class Settings(BaseSettings):
3434
mentions_interval_minutes: int = 20
3535
enable_image_generation: bool = True
3636

37+
# Unified Agent (new architecture)
38+
use_unified_agent: bool = True
39+
agent_interval_minutes: int = 30
40+
3741

3842
# Global settings instance
3943
settings = Settings()

0 commit comments

Comments
 (0)