Agent Loop: Message Processing Pipeline
The AgentLoop is the central orchestrator in PocketPaw. It consumes messages from the bus, builds context, delegates to the agent backend, and streams responses back.
Responsibilities
The AgentLoop handles:
- Message consumption — Subscribes to
InboundMessageevents from the bus - Security checks — Runs Guardian AI and injection scanner on incoming messages
- Context building — Assembles system prompt with identity, memory, and state
- Backend delegation — Routes to the configured agent backend via
AgentRouter - Response streaming — Publishes
OutboundMessagechunks back to the bus - Memory updates — Saves conversation history and triggers Mem0 auto-learn
- System events — Emits tool activity events for the web dashboard
- Inbox updates — Publishes cross-channel inbox events
Processing Flow
# Simplified AgentLoop flowasync def handle_message(self, message: InboundMessage): # 1. Security check threat = await self.guardian.check(message.content) if threat.level >= ThreatLevel.HIGH: await self.bus.publish(OutboundMessage( content="Message blocked by safety check", ... )) return
# 2. Injection scan if await self.scanner.scan(message.content): return # Block injected messages
# 3. Build context context = await self.context_builder.build( session_id=message.session_id, user_query=message.content, )
# 4. Delegate to backend async for event in self.router.process(context, message.content): if event.type == "message": await self.bus.publish(OutboundMessage( content=event.content, is_stream_chunk=True, ... )) elif event.type == "tool_use": await self.bus.publish(SystemEvent( event_type="tool_start", data=event.metadata, ))
# 5. Save to memory await self.memory.save(message.session_id, message.content, response)
# 6. Trigger Mem0 auto-learn (background task) if settings.mem0_auto_learn: asyncio.create_task(self.mem0.learn(message.content, response))Agent Router
The AgentRouter uses a backend registry to select and delegate to one of six backends:
| Backend | Setting Value | Best For |
|---|---|---|
| Claude Agent SDK | claude_agent_sdk | Coding, complex reasoning, built-in tools |
| OpenAI Agents SDK | openai_agents | GPT models, Ollama local inference |
| Google ADK | google_adk | Gemini models, native MCP |
| Codex CLI | codex_cli | Code-focused tasks |
| OpenCode | opencode | External server architecture |
| Copilot SDK | copilot_sdk | Multi-provider (Copilot, OpenAI, Azure, Anthropic) |
The router lazily imports the selected backend via registry.get_backend_class() to avoid loading unused dependencies. Legacy backend names (e.g., pocketpaw_native, open_interpreter) are automatically mapped to their replacements.
Response Standardization
All backends yield standardized AgentEvent objects:
AgentEvent( type="message", # message, tool_use, tool_result, error, done content="...", # Text content metadata={ # Backend-specific metadata "tool_name": "...", "tool_input": {...}, })The AgentEvent dataclass is defined in agents/backend.py alongside the AgentBackend protocol and Capability flags. This ensures consistent behavior regardless of which backend is active.
Identity Drift Prevention
Long conversations can cause models to gradually drift from their configured identity, tone, and communication style. PocketPaw prevents this through two mechanisms:
Structural Placement
The system prompt is structured so identity stays close to the conversation:
- Tool instructions go first as background reference material
- Identity block goes last, wrapped in
<identity>XML tags, positioned closest to the live conversation turns where the model pays the most attention
# Instructions[Tool documentation, knowledge base...]
<identity># Identity: PocketPaw[Personality, soul, communication style, user profile...]</identity>Periodic Reinforcement
When a conversation reaches 20+ turns (user and assistant messages combined), a compact identity reminder is automatically appended to the system prompt:
# Identity ReminderRegardless of conversation length, you remain the agent described in the<identity> block above. Maintain your defined personality, tone, andcommunication style consistently throughout this conversation.This nudges the model back on character without re-injecting the full identity (which would waste context window). The reminder is applied once at the threshold and persists for the rest of the session.
Kill Command
Send /kill or !kill from any channel to cancel the active agent task for the current session. The kill command is intercepted before the session lock, so it works even when the agent is mid-processing. Cancellations are logged to the audit log.
AGENTS.md Constraints
When a message includes file context (e.g., from the desktop client), PocketPaw searches for an AGENTS.md file in the project directory tree and injects its constraints into the system prompt. See AGENTS.md Support for details.
Concurrency
The AgentLoop processes one message per session at a time. If a new message arrives while processing, it’s queued. Different sessions can be processed concurrently.