Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,22 @@ LLM_API_KEY=your_api_key_here
# Any other OpenAI-compatible endpoint
# LLM_BASE_URL=https://openrouter.ai/api/v1

# Bot Behavior Configuration
# Channel to listen to (0 for General, or specific channel name/number)
LISTEN_CHANNEL=0
# Optional custom prompt file for domain-specific knowledge
# CUSTOM_PROMPT_FILE=prompts/custom.txt
# Optional LLM Configuration (Advanced)
# These have sensible defaults but can be customized if needed
# LLM_MAX_TOKENS=500 # Maximum tokens for LLM responses
# LLM_TEMPERATURE=0.7 # LLM temperature (0.0-2.0, lower = more focused)
# LLM_MAX_MESSAGE_LENGTH=120 # Maximum message length in characters
# Optional system prompt file (default: prompts/default.md)
# Use this to specify a custom system prompt file
# LLM_PROMPT_FILE=prompts/custom.md

# MeshCore Configuration
MESHCORE_CONNECTION_TYPE=mock
# Node name - will be set as advertised name on startup
# Bot will respond to DMs and @NodeName mentions in channels
MESHCORE_NODE_NAME=MeshBot
# Channel to listen to (0 for General, or specific channel name/number)
MESHCORE_LISTEN_CHANNEL=0
# MESHCORE_PORT=/dev/ttyUSB0
# MESHCORE_HOST=192.168.1.100
# MESHCORE_BAUDRATE=115200
Expand Down
16 changes: 9 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,13 @@ marimo/_static/
marimo/_lsp/
__marimo__/

# MeshBot specific data files
# Ignore data directory except for system_prompt.txt
data/*
!data/system_prompt.txt
# Ignore node and channel data directories
data/nodes/
data/channels/
# MeshBot specific files
# Ignore all data files (user data, messages, adverts)
data/

# Ignore custom prompts but track default prompt
prompts/*
!prompts/default.md

# Ignore logs
logs/
33 changes: 15 additions & 18 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ pytest tests/test_basic.py -v

2. **AI Agent** (`src/meshbot/agent.py`)
- Pydantic AI agent with rich tool set
- **Utility tools**: calculate, get_current_time, search_history, get_bot_status
- **Utility tools**: calculate, get_current_time, get_bot_status
- **Fun tools**: roll_dice, flip_coin, random_number, magic_8ball
- **Network/mesh tools**: status_request, get_contacts, get_user_info, get_conversation_history
- **Query tools**: search_messages (for historical searches)
- **Network/mesh tools**: get_contacts, get_conversation_history
- **Query tools**: search_adverts, get_node_info, list_nodes (for historical network data)
- Dependency injection system
- Structured responses
- Automatic message splitting for MeshCore length limits
Expand All @@ -128,7 +128,7 @@ pytest tests/test_basic.py -v
3. **Memory System** (`src/meshbot/memory.py` + `src/meshbot/storage.py`)
- File-based storage using text files and CSV
- Data stored in `data/` directory
- System prompt in `data/system_prompt.txt` (editable)
- System prompt in `prompts/default.md` (editable, configurable via `LLM_PROMPT_FILE` env var or `--llm-prompt` CLI arg)
- Network adverts in `data/adverts.csv` (append-only)
- Per-user message history in `data/{pubkey}_messages.txt`
- Per-user memories in `data/{pubkey}_memory.json`
Expand Down Expand Up @@ -224,8 +224,8 @@ pytest tests/ -k "integration" -v
# Test CLI with mock connection
meshbot test user1 "hello" --meshcore-connection-type mock

# Test with custom prompt file
meshbot test user1 "hello" --custom-prompt my_prompt.txt --meshcore-connection-type mock
# Test with custom system prompt file
meshbot test user1 "hello" --llm-prompt my_prompt.md --meshcore-connection-type mock

# Test with custom node name
meshbot test user1 "hello" --meshcore-node-name TestBot --meshcore-connection-type mock
Expand Down Expand Up @@ -253,8 +253,9 @@ meshbot/
│ ├── storage.py # File-based storage layer
│ ├── config.py # Configuration management
│ └── main.py # CLI entry point
├── prompts/ # System prompts directory
│ └── default.md # Default system prompt (tracked in git)
├── data/ # Data directory (auto-created)
│ ├── system_prompt.txt # System prompt (tracked in git)
│ ├── adverts.csv # Network advertisements (gitignored)
│ ├── {pubkey}_messages.txt # Message history per user (gitignored)
│ └── {pubkey}_memory.json # User memories per user (gitignored)
Expand Down Expand Up @@ -324,7 +325,6 @@ The agent currently has the following tools implemented in `src/meshbot/agent.py
**Utility Tools**:
- `calculate`: Perform mathematical calculations using Python's eval (safely)
- `get_current_time`: Return current date and time
- `search_history`: Search conversation history for keywords
- `get_bot_status`: Return bot uptime and connection status

**Fun Tools**:
Expand All @@ -334,13 +334,10 @@ The agent currently has the following tools implemented in `src/meshbot/agent.py
- `magic_8ball`: Ask the magic 8-ball for wisdom

**Network/Mesh Tools**:
- `status_request`: Send status request to a node (ping equivalent)
- `get_contacts`: List all MeshCore contacts with names
- `get_user_info`: Get user statistics from chat logs
- `get_conversation_history`: Retrieve recent messages with a user

**Query Tools** (Historical Data):
- `search_messages`: Search messages across all conversations
- `search_adverts`: Search advertisement history with filters (node_id, time range)
- `get_node_info`: Get detailed info about a specific mesh node (status, activity, stats)
- `list_nodes`: List all known nodes with filters (online_only, has_name)
Expand Down Expand Up @@ -383,16 +380,16 @@ The agent includes network situational awareness implemented in `src/meshbot/mes

### Modifying Memory System
The memory system uses file-based storage in the `data/` directory:
- **System prompt**: `data/system_prompt.txt` - editable system prompt for the agent
- **System prompt**: `prompts/default.md` - editable system prompt for the agent (Markdown format, configurable)
- **Adverts file**: `data/adverts.csv` - network advertisements (append-only CSV)
- **Message files**: `data/{pubkey}_messages.txt` - message history per user (pipe-delimited)
- **Memory files**: `data/{pubkey}_memory.json` - user memories and metadata per user (JSON)

**File Formats** (see `src/meshbot/storage.py`):
- **system_prompt.txt**: Plain text, editable system prompt loaded on startup
- **adverts.csv**: CSV with columns: timestamp, node_id, node_name, signal_strength, details
- **{pubkey}_messages.txt**: Pipe-delimited: timestamp|message_type|role|content|sender
- **{pubkey}_memory.json**: JSON with fields: name, is_online, first_seen, last_seen, last_advert, total_adverts
**File Formats**:
- **prompts/default.md**: Markdown format system prompt loaded on startup (see `src/meshbot/agent.py`)
- **adverts.csv**: CSV with columns: timestamp, node_id, node_name, signal_strength, details (see `src/meshbot/storage.py`)
- **{pubkey}_messages.txt**: Pipe-delimited: timestamp|message_type|role|content|sender (see `src/meshbot/storage.py`)
- **{pubkey}_memory.json**: JSON with fields: name, is_online, first_seen, last_seen, last_advert, total_adverts (see `src/meshbot/storage.py`)

**Efficiency**:
- Adverts: Only reads last N lines using deque (efficient for large files)
Expand All @@ -410,7 +407,7 @@ To modify:
2. Update `src/meshbot/memory.py` for interface changes
3. Update `src/meshbot/meshcore_interface.py` for event handling
4. Update `src/meshbot/agent.py` for new tools
5. Edit `data/system_prompt.txt` to customize agent behavior
5. Edit `prompts/default.md` to customize agent behavior (or create custom prompt and use `--llm-prompt` or `LLM_PROMPT_FILE` env var)
6. Update documentation if interface changes

## 🚨 Troubleshooting
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ docker run -it --rm \
- **📡 MeshCore Integration**: Communicates via MeshCore network (serial, TCP, BLE, or mock)
- **🧠 Simple Memory System**: Text file-based chat logs (1000 lines per conversation)
- **💬 Smart Messaging**: Automatic message splitting with length limits (configurable, default 120 chars)
- **🔧 Rich Tool System**: Utility tools (calculator, time, history) and fun tools (dice, coin, 8-ball, random numbers)
- **🔧 Rich Tool System**: Utility tools (calculator, time, bot status) and fun tools (dice, coin, 8-ball, random numbers)
- **🌐 Network Awareness**: Real-time tracking of mesh network events (adverts, contacts, paths, status)
- **👥 Contact Tracking**: Automatic node name discovery and mapping from mesh advertisements
- **📊 Situational Context**: Network events and node names included in LLM context for awareness
Expand Down Expand Up @@ -182,9 +182,9 @@ meshbot

3. **AI Agent** (`agent.py`)
- Pydantic AI agent with rich tool set
- Utility tools (calculate, time, search history, bot status)
- Utility tools (calculate, time, bot status)
- Fun tools (dice, coin, random numbers, magic 8-ball)
- Network/mesh tools (status requests, contact management)
- Network/mesh tools (contact management, conversation history, node queries)
- Structured responses with message splitting
- API request limits (max 5 per message)
- Network context injection for situational awareness
Expand Down Expand Up @@ -364,7 +364,6 @@ The agent has access to three categories of built-in tools:
### Utility Tools
- **Calculate**: Perform mathematical calculations (e.g., "calculate 25 * 4 + 10")
- **Get Current Time**: Get the current date and time
- **Search History**: Search conversation history for specific topics
- **Get Bot Status**: Check bot uptime and connection status

### Fun Tools
Expand All @@ -374,10 +373,12 @@ The agent has access to three categories of built-in tools:
- **Magic 8-Ball**: Ask the magic 8-ball for wisdom

### Network/Mesh Tools
- **Status Request**: Send status request to nodes (ping equivalent)
- **Get Contacts**: List available MeshCore contacts with their names
- **Get User Info**: Retrieve user statistics from chat logs
- **Conversation History**: Access recent messages with a user
- **Get Channel Messages**: Retrieve recent messages from a channel
- **Get User Messages**: Access recent private messages with a user
- **Get Node Info**: Get detailed information about a specific mesh node
- **List Nodes**: List all known nodes with optional filters
- **List Adverts**: Search advertisement history with filters

### Network Awareness
The agent automatically receives context about:
Expand Down
34 changes: 34 additions & 0 deletions prompts/default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# MeshBot System Prompt

You are MeshBot, an AI assistant that communicates through the MeshCore network. You are helpful, concise, and knowledgeable.

## MeshCore Network Limitations

MeshCore is a simple text messaging system with some limitations:

- Keep responses concise and clear (prefer under 200 chars, max 120)
- Use newlines for better readability when helpful
- NO emoji, but you CAN use basic punctuation like • — – for lists and separation
- Use plain text with good structure
- Be direct and helpful

## Tool Usage Guidelines

- Use tools ONLY when absolutely necessary - prefer direct responses
- Maximum 1-2 tool calls per message, avoid chains
- For simple questions, respond directly without tools
- IMPORTANT: When calling weather API, make the HTTP request INSIDE the tool, don't call the tool repeatedly
- CRITICAL: get_weather tool makes HTTP request automatically - call it ONCE only

## Special Behaviors

When users send 'ping', respond with 'pong'

## Examples of Good Formatting

```
Status: Connected • 20 contacts online • 51 messages processed
Time: 14:30 • Date: 2025-01-15
Result: Success • Data saved • Ready for next task
Nodes found: 12 online • 8 with names • 4 new today
```
66 changes: 15 additions & 51 deletions src/meshbot/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(
data_dir: Optional[Path] = None,
meshcore_connection_type: str = "mock",
listen_channel: str = "0",
custom_prompt: Optional[str] = None,
system_prompt_file: Optional[Path] = None,
base_url: Optional[str] = None,
max_message_length: int = 120,
node_name: Optional[str] = None,
Expand All @@ -61,7 +61,7 @@ def __init__(
self.data_dir = data_dir
self.meshcore_connection_type = meshcore_connection_type
self.listen_channel = listen_channel
self.custom_prompt = custom_prompt
self.system_prompt_file = system_prompt_file or Path("prompts/default.md")
self.base_url = base_url
self.max_message_length = max_message_length
self.node_name = node_name
Expand Down Expand Up @@ -129,56 +129,20 @@ async def initialize(self) -> None:
)
await self.memory.load()

# Load system prompt from file
data_dir = self.data_dir or Path("data")
system_prompt_file = data_dir / "system_prompt.txt"

# Create default system prompt if it doesn't exist
if not system_prompt_file.exists():
data_dir.mkdir(parents=True, exist_ok=True)
default_prompt = (
"You are MeshBot, an AI assistant that communicates through the MeshCore network. "
"You are helpful, concise, and knowledgeable. "
"MeshCore is a simple text messaging system with some limitations:\n"
f"- Keep responses concise and clear (prefer under 200 chars, max {self.max_message_length})\n"
"- Use newlines for better readability when helpful\n"
"- NO emoji, but you CAN use basic punctuation like • — – for lists and separation\n"
"- Use plain text with good structure\n"
"- Be direct and helpful\n"
"- Use tools ONLY when absolutely necessary - prefer direct responses\n"
"- Maximum 1-2 tool calls per message, avoid chains\n"
"- For simple questions, respond directly without tools\n"
"- IMPORTANT: When calling weather API, make the HTTP request INSIDE the tool, don't call the tool repeatedly\n"
"- CRITICAL: get_weather tool makes HTTP request automatically - call it ONCE only\n"
"When users send 'ping', respond with 'pong'\n"
"\n"
"Examples of good formatting:\n"
"Status: Connected • 20 contacts online • 51 messages processed\n"
"Time: 14:30 • Date: 2025-01-15\n"
"Result: Success • Data saved • Ready for next task\n"
"Nodes found: 12 online • 8 with names • 4 new today\n"
)
with open(system_prompt_file, "w", encoding="utf-8") as f:
f.write(default_prompt)
logger.info(f"Created default system prompt: {system_prompt_file}")

# Load system prompt from file
try:
with open(system_prompt_file, "r", encoding="utf-8") as f:
base_instructions = f.read()
logger.info(f"Loaded system prompt from: {system_prompt_file}")
if not self.system_prompt_file.exists():
raise FileNotFoundError(
f"System prompt file not found: {self.system_prompt_file}"
)

with open(self.system_prompt_file, "r", encoding="utf-8") as f:
instructions = f.read()
logger.info(f"Loaded system prompt from: {self.system_prompt_file}")
except Exception as e:
logger.error(f"Error loading system prompt: {e}")
# Fall back to default
base_instructions = "You are MeshBot, an AI assistant that communicates through the MeshCore network."

# Add custom prompt if provided
if self.custom_prompt:
instructions = (
f"{base_instructions}\n\nAdditional Context:\n{self.custom_prompt}"
)
else:
instructions = base_instructions
# Fall back to minimal default
instructions = "You are MeshBot, an AI assistant that communicates through the MeshCore network."

# Set base URL for custom endpoints if provided
if self.base_url:
Expand Down Expand Up @@ -636,9 +600,9 @@ async def _handle_action(
) -> None:
"""Handle additional actions from the agent."""
try:
if action == "ping" and action_data and "destination" in action_data:
await self.meshcore.ping_node(action_data["destination"])
# Add more action handlers as needed
# Action handling infrastructure reserved for future use
# Add action handlers as needed
pass
except Exception as e:
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is unreachable.

Copilot uses AI. Check for mistakes.
logger.error(f"Error handling action {action}: {e}")

Expand Down
26 changes: 15 additions & 11 deletions src/meshbot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class MeshCoreConfig:
node_name: Optional[str] = field(
default_factory=lambda: os.getenv("MESHCORE_NODE_NAME", "MeshBot")
)
listen_channel: str = field(
default_factory=lambda: os.getenv("MESHCORE_LISTEN_CHANNEL", "0")
)
port: Optional[str] = field(default_factory=lambda: os.getenv("MESHCORE_PORT"))
baudrate: int = field(
default_factory=lambda: int(os.getenv("MESHCORE_BAUDRATE", "115200"))
Expand Down Expand Up @@ -51,24 +54,25 @@ class AIConfig:
api_key: Optional[str] = field(default_factory=lambda: os.getenv("LLM_API_KEY"))
base_url: Optional[str] = field(default_factory=lambda: os.getenv("LLM_BASE_URL"))
max_tokens: int = field(
default_factory=lambda: int(os.getenv("AI_MAX_TOKENS", "500"))
default_factory=lambda: int(os.getenv("LLM_MAX_TOKENS", "500"))
)
temperature: float = field(
default_factory=lambda: float(os.getenv("AI_TEMPERATURE", "0.7"))
)
listen_channel: str = field(
default_factory=lambda: os.getenv("LISTEN_CHANNEL", "0")
default_factory=lambda: float(os.getenv("LLM_TEMPERATURE", "0.7"))
)
max_message_length: int = field(
default_factory=lambda: int(os.getenv("MAX_MESSAGE_LENGTH", "120"))
default_factory=lambda: int(os.getenv("LLM_MAX_MESSAGE_LENGTH", "120"))
)
custom_prompt_file: Optional[Path] = field(default=None)
system_prompt_file: Optional[Path] = field(default=None)

def __post_init__(self) -> None:
"""Post-initialization to handle custom_prompt_file."""
prompt_file_env = os.getenv("CUSTOM_PROMPT_FILE")
if prompt_file_env and not self.custom_prompt_file:
self.custom_prompt_file = Path(prompt_file_env)
"""Post-initialization to handle system prompt file."""
# Handle system prompt file
system_prompt_env = os.getenv("LLM_PROMPT_FILE")
if system_prompt_env and not self.system_prompt_file:
self.system_prompt_file = Path(system_prompt_env)
elif not self.system_prompt_file:
# Default to prompts/default.md
self.system_prompt_file = Path("prompts/default.md")


@dataclass
Expand Down
Loading
Loading