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:

  1. Message consumption — Subscribes to InboundMessage events from the bus
  2. Security checks — Runs Guardian AI and injection scanner on incoming messages
  3. Context building — Assembles system prompt with identity, memory, and state
  4. Backend delegation — Routes to the configured agent backend via AgentRouter
  5. Response streaming — Publishes OutboundMessage chunks back to the bus
  6. Memory updates — Saves conversation history and triggers Mem0 auto-learn
  7. System events — Emits tool activity events for the web dashboard
  8. Inbox updates — Publishes cross-channel inbox events

Processing Flow

# Simplified AgentLoop flow
async 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:

BackendSetting ValueBest For
Claude Agent SDKclaude_agent_sdkCoding, complex reasoning, built-in tools
OpenAI Agents SDKopenai_agentsGPT models, Ollama local inference
Google ADKgoogle_adkGemini models, native MCP
Codex CLIcodex_cliCode-focused tasks
OpenCodeopencodeExternal server architecture
Copilot SDKcopilot_sdkMulti-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:

  1. Tool instructions go first as background reference material
  2. 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 Reminder
Regardless of conversation length, you remain the agent described in the
<identity> block above. Maintain your defined personality, tone, and
communication 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.