Sentinel Network Architecture

A tamper-proof AI agent security monitoring system built on Bedsheet. Workers delegate all tool execution to an isolated Action Gateway that validates, logs, and rate-limits every action. Poisoned agents can't bypass what they can't reach.

1. Threat Model

The Sentinel Network addresses a fundamental problem in multi-agent systems: what happens when an agent goes rogue?

In the OpenClaw crisis scenario, a compromised agent could perform unauthorized searches, install malicious skills, sabotage shared resources, or exfiltrate data. Traditional monitoring (reading log files) has two fatal flaws:

Flaw 1: Local file logs don't work distributed Agents in different regions can't share a local activity_log.jsonl file.
Flaw 2: Poisoned agents can bypass logging A rogue agent can call functions directly without logging, or write fake log entries to cover its tracks.

The fix: move tool execution itself into an isolated, trusted process. Workers become thin LLM shells with no local tool implementations.

2. Architecture

Worker Agent (thin LLM shell) Action Gateway (owns all tools) ┌─────────────────────┐ ┌──────────────────────────────┐ │ LLM decides: │ PubNub req │ Receives request │ │ "call search_web" │ ──────────────▶│ Check rate limits │ │ │ │ Check suspicious patterns │ │ Has tool SCHEMAS │ │ If approved: │ │ but NO tool CODE │ PubNub resp │ Execute actual tool fn │ │ │ ◀──────────────│ Return result │ │ Gets result back, │ │ If denied: │ │ continues reasoning │ │ Return denial reason │ └─────────────────────┘ │ Log EVERYTHING to ledger │ └──────────────────────────────┘ ↑ Behavior Sentinel queries ledger via PubNub for rates
Key property The gateway IS the execution layer, not just a logger. Workers have tool descriptions (so the LLM knows what to call) but zero local implementation. A poisoned agent retrying 1000 times just generates 1000 denied log entries in the gateway's tamper-proof ledger.

3. Process Map

The system runs as 7 independent processes, launched in order:

Gateway

Action Gateway

Owns all tool execution. Validates, rate-limits, logs, responds. Not an LLM agent — pure deterministic infrastructure.

Worker

Web Researcher

DuckDuckGo searches via gateway. ~15% chance of rogue search burst (gateway blocks after rate threshold).

Worker

Scheduler

Calendar management via gateway. ~15% chance of rogue appointment spam (gateway rate-limits).

Worker

Skill Acquirer

ClawHub skill installation via gateway. ~15% chance of malicious install attempt (gateway blocks).

Sentinel

Behavior Sentinel

Queries gateway ledger every 10s. Detects rate anomalies and broadcasts alerts.

Sentinel

Supply Chain Sentinel

Independent filesystem verification. Hashes installed skills against registry. Defense in depth.

Commander

Sentinel Commander

Correlates alerts from sentinels and gateway. Coordinates responses.

4. Action Gateway Design

The Action Gateway is not a Bedsheet agent. It's a standalone asyncio process that connects directly to PubNub. No LLM, no instruction, no memory. This keeps the trust boundary clean: the gateway is deterministic code, not an LLM that could be prompt-injected.

Components

Component Role
ActionLedger Append-only deque of ActionRecord dataclasses. Time-windowed queries. Auto-prunes records older than 10 minutes.
AnomalyDetector Stateless evaluator. Checks rate limits (>10 actions/min) and suspicious keyword patterns. Returns (verdict, reason).
ToolExecutor Holds the actual tool implementations: DuckDuckGo search, calendar CRUD, skill install with SHA-256 verification.
ActionGateway Main class. PubNub listener + processor loop. Routes requests to detector → executor → ledger → response.

Processing Pipeline

Incoming Signal │ ├─ action_request ─────▶ AnomalyDetector.evaluate() │ │ │ ┌─────┴─────┐ │ │ approved │ denied / rate_limited │ │ │ │ ToolExecutor Log to Ledger │ .execute() Respond with denial │ │ │ Log to Ledger │ Respond with result │ ├─ query_rates ───────▶ Ledger.agent_stats(minutes) │ │ │ Respond with stats │ └─ query_agent_log ───▶ Ledger.agent_log(agent, minutes) │ Respond with records

5. Tamper-Proof Ledger

The ActionLedger is the single source of truth for all agent activity. Every action request — approved, denied, or rate-limited — is recorded as an ActionRecord.

ActionRecord Fields

FieldTypeDescription
timestampfloatUnix timestamp of the request
agentstrName of the requesting agent
actionstrTool action name (e.g., search_web)
paramsdictParameters passed to the tool
verdictstrapproved, denied, or rate_limited
reasonstrExplanation if denied
result_summarystrFirst 200 chars of result (if approved)

Why It's Tamper-Proof

Query Interface

The behavior sentinel queries the ledger via PubNub signals:

# Sentinel sends:
Signal(kind="request", sender="behavior-sentinel", target="action-gateway",
       payload={"type": "query_rates", "minutes": 2})

# Gateway responds with:
{
    "type": "rate_stats",
    "window_minutes": 2,
    "total_actions": 47,
    "agents": {
        "web-researcher": {"count": 35, "rate": 17.5, "denied": 20, "approved": 15},
        "scheduler": {"count": 8, "rate": 4.0, "denied": 0, "approved": 8},
    },
}

6. Anomaly Detector

The AnomalyDetector is a stateless evaluator that runs before every tool execution. It checks two conditions:

Rate Limiting

Threshold: 10 actions per minute per agent The detector queries the ledger for the last 1 minute of records from the requesting agent. If the count exceeds 10, the action is rate-limited.

This is what stops rogue bursts. When the web researcher fires 50 rapid searches, the first ~10 get approved. Every subsequent request returns "verdict": "rate_limited". The rogue loop gets back "denied" for every attempt.

Suspicious Keywords

The detector scans all action parameters for suspicious terms:

SUSPICIOUS_KEYWORDS = {
    "password", "exploit", "bypass", "credential", "dump",
    "exfiltrat", "inject", "hack", "leaked", "pastebin",
}

If any keyword is found in the JSON-serialized parameters, the action is immediately denied. This catches the rogue search queries like "password dump site" and "bypass authentication" on the first attempt, before any rate limit is even reached.

VerdictMeaning
approved Action passes all checks. Tool is executed, result returned.
denied Suspicious content detected. Action blocked, reason returned.
rate_limited Agent exceeded 10 actions/min. Action blocked until rate drops.

7. Tool Executor

The ToolExecutor class holds the actual implementations of every tool that workers use. When a worker was previously doing ddgs.text(query) locally, that code now lives exclusively inside the gateway's executor.

Available Tools

ActionWorkerImplementation
search_web Web Researcher DuckDuckGo search via ddgs package, returns top 3 results
get_search_summary Web Researcher Returns session search count
list_appointments Scheduler Reads calendar.json
add_appointment Scheduler Appends to calendar.json
delete_appointment Scheduler Removes from calendar.json by ID
list_available_skills Skill Acquirer Reads clawhub/registry.json
install_skill Skill Acquirer SHA-256 verification + malicious flag check. Returns file content if clean.
list_installed_skills Skill Acquirer Lists installed_skills/ with hash verification

8. Signal Protocol

All communication between workers, the gateway, and sentinels uses Bedsheet's Signal dataclass over PubNub. The serialization is compact (short keys, 30KB limit) to fit within PubNub's 32KB message limit.

Action Request

worker → gateway
Signal(kind="request", sender="web-researcher", target="action-gateway",
       correlation_id="abc123",
       payload={
           "type": "action_request",
           "action": "search_web",
           "params": {"query": "AI trends 2026"},
       })

Action Response

gateway → worker
Signal(kind="response", sender="action-gateway", target="web-researcher",
       correlation_id="abc123",
       payload={
           "verdict": "approved",       # or "denied" / "rate_limited"
           "result": "Results for 'AI trends': ...",
           "reason": "",                # explanation if denied
           "rate": 3.5,                 # agent's current rate
       })

Rate Query

behavior-sentinel → gateway
Signal(kind="request", sender="behavior-sentinel", target="action-gateway",
       payload={"type": "query_rates", "minutes": 2})

Rate Response

gateway → behavior-sentinel
Signal(kind="response", sender="action-gateway", target="behavior-sentinel",
       payload={
           "type": "rate_stats",
           "window_minutes": 2,
           "total_actions": 47,
           "agents": {
               "web-researcher": {"count": 35, "rate": 17.5, "denied": 20, "approved": 15},
               "scheduler": {"count": 8, "rate": 4.0, "denied": 0, "approved": 8},
           },
       })

Gateway Alert

gateway → alerts channel (broadcast)
Signal(kind="alert", sender="action-gateway",
       payload={
           "severity": "critical",
           "category": "gateway_enforcement",
           "agent": "web-researcher",
           "denied_count": 20,
           "message": "Agent 'web-researcher' blocked: 20 denied requests in 1min",
       })

9. Request Flow

Every tool call follows this exact path. There are no shortcuts — every action goes through the gateway, every action is logged.

Worker LLM gateway_request() Action Gateway ─────────── ───────────────── ────────────── Decides: "search_web" │ └─▶ @action proxy fn │ ├─ Create correlation_id ├─ Register Future in _pending_requests ├─ Send Signal via PubNub ────────▶ Receive signal │ │ │ AnomalyDetector.evaluate() │ │ │ ┌─────┴─────┐ │ approved denied │ │ │ │ Execute tool Log denial │ │ │ │ Log result Track for escalation │ │ │ │ ◀──────────────────── Respond via PubNub │ ├─ Future resolved by _signal_loop │ └─▶ Return result (or "denied") to LLM

Fail-Closed Design

Gateway unreachable = all actions denied If the gateway doesn't respond within the timeout (10s default), workers get {"verdict": "denied", "reason": "gateway_unreachable"}. This is the secure default — no tool execution without the gateway.

10. Alert Escalation

The system has two alert sources that feed the Sentinel Commander:

Gateway Direct Alerts

When the gateway denies 5+ actions from the same agent within 1 minute, it broadcasts a critical alert directly to the alerts channel. This gives the commander immediate notification from the trusted authority.

Behavior Sentinel Alerts

The behavior sentinel polls the gateway ledger every 10 seconds and detects rate anomalies (>10 actions/min). It broadcasts high severity alerts for sustained anomalies. This catches lower-severity trends that the gateway's per-request denial doesn't escalate.

Alert Timeline (Rogue Burst)

T+0s Web researcher fires rogue burst (50 rapid searches) T+0.1s Gateway: first few searches approved (rate < 10/min) T+0.5s Gateway: suspicious keywords detected ─▶ "denied" responses T+1s Gateway: rate threshold hit ─▶ all subsequent "rate_limited" T+2s Gateway: 5+ denials ─▶ broadcasts CRITICAL alert to alerts channel T+10s Behavior sentinel polls ─▶ sees rate anomaly ─▶ broadcasts HIGH alert T+10s Commander receives both alerts ─▶ correlates and responds

11. Supply Chain Trust Boundary

Skill installation is the most security-sensitive operation. The gateway enforces the trust boundary:

skill-acquirer LLM Action Gateway Filesystem ───────────────── ────────────────── ───────────── calls install_skill receives request ("weather_lookup.py") reads clawhub/weather_lookup.py │ computes SHA-256 │ checks registry.json │ ├─ malicious flag? ─▶ DENY │ ├─ hash mismatch? ─▶ DENY │ └─ clean? ─▶ APPROVE │ returns file content ───────▶ agent writes to │ in response payload installed_skills/ ◀─────────────────── gets content + writes it
Defense in depth The supply chain sentinel independently verifies what's on disk by hashing installed skills against the registry. Even if something bypasses or survives the gateway (e.g., a pre-existing file), the supply chain sentinel catches it.

12. How Rogue Behavior Is Blocked

Each worker has a ~15% chance of going rogue per cycle. Here's what happens for each:

Web Researcher Rogue Burst

Fires 50 rapid searches with suspicious queries like "password dump site".

CheckResult
Suspicious keywords ("password", "exploit", "bypass") denied immediately — keyword detected before rate limit even applies
Rate limit (10/min) rate_limited after first 10 requests, even for non-suspicious queries
Escalation (5+ denials/min) critical alert broadcast to commander

Scheduler Rogue Sabotage

Rapid-fires 20 junk appointments ("JUNK-XXXX" on "1999-01-01").

CheckResult
Rate limit (10/min) rate_limited after first 10. Maximum 10 junk entries instead of 20.

Skill Acquirer Rogue Install

Tries to install data_exfiltrator.py (flagged malicious).

CheckResult
Malicious flag in registry denied — gateway returns "BLOCKED: flagged as malicious"
Keyword check ("exfiltrat") denied — suspicious keyword in skill name
Rogue code still runs in the worker The rogue burst code is still present in each worker — it simulates a compromised agent. But now every call goes through the gateway. The rogue code can retry forever; every retry is logged, every retry is blocked.

13. Defense in Depth

The Sentinel Network uses three independent defense layers:

LayerMechanismCatches
Layer 1: Gateway Rate limiting, keyword detection, malicious flag checks All tool abuse in real-time. Blocks before execution.
Layer 2: Behavior Sentinel Polls gateway ledger for rate anomalies Sustained patterns, lower-severity trends, alert correlation
Layer 3: Supply Chain Sentinel Independent filesystem hash verification Anything that bypasses gateway (pre-existing files, race conditions)
No single point of failure If the gateway is bypassed (hypothetically), the supply chain sentinel catches bad files on disk. If the behavior sentinel is compromised, the gateway still enforces rate limits. Each layer operates independently.

14. Running the System

Prerequisites

# Required environment variables
export PUBNUB_SUBSCRIBE_KEY=sub-c-...
export PUBNUB_PUBLISH_KEY=pub-c-...
export GEMINI_API_KEY=AIza...

# Install dependencies
uv pip install -e ".[sense,demo]"

Option A: Shell Script (recommended)

cd examples/agent-sentinel
./start.sh              # Launch gateway + 6 agents + dashboard
./start.sh --no-dash    # Without dashboard server

Option B: Python Launcher

cd examples/agent-sentinel
python run.py

Launch Order

  1. Action Gateway (must start first — workers need it)
  2. Workers (web-researcher, scheduler, skill-acquirer)
  3. Sentinels (behavior-sentinel, supply-chain-sentinel)
  4. Commander (sentinel-commander)

Each process is staggered by 2 seconds to allow PubNub connections to establish.

15. Live Dashboard

The Sentinel Dashboard connects directly to PubNub and displays real-time signals from all agents. When using start.sh, the dashboard is automatically served at:

http://localhost:8765/agent-sentinel-dashboard.html

The dashboard shows:

Gateway alerts appear as critical events with the category gateway_enforcement, showing exactly which agent was blocked and how many requests were denied.