AI Agent Swarm Coordination¶
This guide shows how to use Acteon as a safety and orchestration layer for multi-agent AI systems. By routing every agent action through Acteon's dispatch pipeline, you gain centralized permission control, prompt injection defense, rate limiting, approval gates, failure isolation, and full observability -- without modifying the agents themselves.
Runnable Examples
The examples/agent-swarm-coordination/ directory contains a complete, runnable setup with Claude Code hooks, a PostgreSQL-backed Acteon server, safety rules, rate limiting, approval gates, and Discord notifications. Follow the README there to have a governed agent session running in minutes.
For a multi-agent demo, the swarm/ subdirectory launches three concurrent headless Claude Code sessions (api-builder, test-writer, security-auditor) that share the same Acteon tenant. They collide through throttle counters, dedup keys, quotas, suppress rules, and approval gates -- with a post-run collision report showing the outcome breakdown. Run it with:
flowchart LR
subgraph Agents
A1[Researcher Agent]
A2[Code Agent]
A3[Deploy Agent]
A4[Support Agent]
end
subgraph Acteon
GW[Gateway]
RE[Rule Engine]
LLM[LLM Guardrails]
WASM[WASM Plugins]
QU[Quotas]
AP[Approvals]
CH[Chains]
end
subgraph Providers
P1[GitHub API]
P2[Cloud Deploy]
P3[Slack]
P4[Email]
P5[Database]
end
A1 & A2 & A3 & A4 -->|dispatch| GW
GW --> RE --> LLM --> WASM --> QU --> AP
AP --> CH
CH --> P1 & P2 & P3 & P4 & P5 Every agent call to an external service becomes an Acteon dispatch. The pipeline evaluates rules, runs LLM and WASM guardrails, checks quotas, and gates high-risk operations behind human approval -- all before a single byte leaves your network.
1. Agent Identity and Tenant Isolation¶
Each agent operates as a tenant within a shared Acteon namespace. This gives you per-agent audit trails, per-agent quotas, and per-agent rules -- all with zero changes to the agents themselves.
Dispatch with Agent Metadata¶
Agents identify themselves through the tenant field and carry role information in metadata:
curl -X POST http://localhost:8080/v1/dispatch \
-H "Content-Type: application/json" \
-H "Authorization: Bearer agent-researcher-key" \
-d '{
"namespace": "agent-swarm",
"tenant": "researcher-agent",
"provider": "github",
"action_type": "search_code",
"payload": {
"query": "authentication bypass",
"repositories": ["myorg/backend"]
},
"metadata": {
"agent_id": "researcher-01",
"agent_role": "researcher",
"session_id": "sess-abc123",
"parent_task": "security-audit-42"
}
}'
Authentication Configuration¶
Create a separate API key per agent with scoped permissions:
# Read-only agent -- can dispatch but cannot manage rules or config
[[api_keys]]
key = "agent-researcher-key"
role = "operator"
tenant = "researcher-agent"
description = "Researcher agent -- search and read only"
# Deployment agent -- elevated privileges, still scoped to its tenant
[[api_keys]]
key = "agent-deploy-key"
role = "operator"
tenant = "deploy-agent"
description = "Deploy agent -- can trigger deployments"
# Human supervisor -- full admin access
[[api_keys]]
key = "supervisor-admin-key"
role = "admin"
description = "Human supervisor with full access"
Hot Reload
With watch = true in your [auth] config, you can add or revoke agent API keys without restarting the server. See Authentication for details.
Why Tenants?¶
Using tenants for agent isolation gives you:
| Capability | How |
|---|---|
| Per-agent audit trails | Query audits by tenant=researcher-agent |
| Per-agent rate limits | Quota policies scoped to a tenant |
| Per-agent rules | Conditions on action.tenant |
| Per-agent state | Dedup keys and throttle counters are tenant-scoped |
| Cross-agent visibility | A supervisor can query all tenants in the namespace |
2. Permission Control with Rules¶
Rules form the capability matrix that determines what each agent is allowed to do. By writing rules that match on action.tenant (agent identity), action.action_type (operation), and action.provider (target service), you build fine-grained permission boundaries.
Deny-by-Default Capability Matrix¶
Start with a catch-all suppress rule at the lowest priority, then explicitly allow operations per agent:
rules:
# ── Deny by default ──────────────────────────────
- name: deny-all-agents
priority: 100
description: "Block any agent action not explicitly allowed"
condition:
field: action.namespace
eq: "agent-swarm"
action:
type: suppress
reason: "No matching permission rule"
# ── Researcher: search and read ──────────────────
- name: allow-researcher-search
priority: 10
condition:
all:
- field: action.tenant
eq: "researcher-agent"
- field: action.action_type
in: ["search_code", "read_file", "list_issues"]
action:
type: allow
# ── Code agent: create PRs and branches ─────────
- name: allow-code-agent-pr
priority: 10
condition:
all:
- field: action.tenant
eq: "code-agent"
- field: action.action_type
in: ["create_branch", "create_pr", "push_commit"]
action:
type: allow
# ── Deploy agent: staging only without approval ──
- name: allow-deploy-staging
priority: 10
condition:
all:
- field: action.tenant
eq: "deploy-agent"
- field: action.action_type
eq: "deploy"
- field: action.payload.environment
eq: "staging"
action:
type: allow
# ── Deploy agent: production requires approval ───
- name: approve-deploy-production
priority: 5
condition:
all:
- field: action.tenant
eq: "deploy-agent"
- field: action.action_type
eq: "deploy"
- field: action.payload.environment
eq: "production"
action:
type: require_approval
message: "Production deployment by deploy-agent requires human approval"
ttl_seconds: 1800
# ── Support agent: messaging only ────────────────
- name: allow-support-messaging
priority: 10
condition:
all:
- field: action.tenant
eq: "support-agent"
- field: action.action_type
in: ["send_email", "send_slack", "create_ticket"]
action:
type: allow
// Deny by default
action.namespace == "agent-swarm"
? suppress("No matching permission rule")
: allow()
// Researcher: search and read
action.tenant == "researcher-agent"
&& action.action_type in ["search_code", "read_file", "list_issues"]
? allow()
// Code agent: create PRs and branches
action.tenant == "code-agent"
&& action.action_type in ["create_branch", "create_pr", "push_commit"]
? allow()
Rule Priority
Lower priority numbers evaluate first. The deny-all rule at priority 100 only fires if no higher-priority rule matched. See Rule System for details.
Testing Permissions in the Playground¶
Use the Rule Playground to verify your capability matrix before deploying:
curl -X POST http://localhost:8080/v1/rules/evaluate \
-H "Content-Type: application/json" \
-d '{
"namespace": "agent-swarm",
"tenant": "researcher-agent",
"provider": "github",
"action_type": "delete_repository",
"payload": {"repo": "myorg/backend"}
}'
Expected response: the deny-all-agents rule fires, confirming the researcher cannot delete repositories.
3. Prompt Injection Prevention¶
AI agents that process user input are vulnerable to prompt injection attacks where malicious instructions are embedded in data. Acteon provides three layers of defense that run in the dispatch pipeline before any action reaches a provider.
flowchart TD
A[Agent dispatches action] --> B{WASM Plugin}
B -->|Clean| C{LLM Guardrail}
B -->|Injection detected| D[Suppress]
C -->|Safe| E{Semantic Routing}
C -->|Suspicious| F[Route to review queue]
E -->|Normal topic| G[Execute]
E -->|Anomalous topic| H[Flag for review] Layer 1: WASM Plugin for Pattern Detection¶
A lightweight WASM plugin scans action payloads for common injection patterns (role switching, instruction overrides, encoded payloads) at near-native speed. This catches the obvious attacks before the more expensive LLM check.
Critically, WASM plugins run inside a strict sandbox powered by Wasmtime. Each plugin invocation has no access to the filesystem, network, or host environment. Resource limits cap memory usage (default 16 MB) and CPU time (default 100 ms via fuel metering). This means even a maliciously crafted plugin cannot exfiltrate data, open network connections, or starve the gateway of resources -- making WASM the ideal execution environment for untrusted or third-party detection logic.
// injection_detector plugin (compiled to .wasm)
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct ActionContext {
payload: serde_json::Value,
}
#[derive(Serialize)]
struct PluginResult {
verdict: bool,
message: Option<String>,
}
static PATTERNS: &[&str] = &[
"ignore previous instructions",
"ignore all prior",
"you are now",
"system prompt:",
"\\x00", // null byte injection
"base64:",
];
#[no_mangle]
pub extern "C" fn evaluate(input_ptr: *const u8, input_len: usize) -> i32 {
let input = unsafe { std::slice::from_raw_parts(input_ptr, input_len) };
let ctx: ActionContext = serde_json::from_slice(input).unwrap();
let text = ctx.payload.to_string().to_lowercase();
let injection_found = PATTERNS.iter().any(|p| text.contains(p));
let result = PluginResult {
verdict: !injection_found, // true = safe, false = blocked
message: if injection_found {
Some("Potential prompt injection detected".into())
} else {
None
},
};
let output = serde_json::to_vec(&result).unwrap();
// The plugin writes `output` into its linear memory and returns the byte
// length. Acteon reads that many bytes from the module's memory to parse
// the JSON result. See the WASM Plugins guide for the full ABI contract.
output.len() as i32
}
Register the plugin and wire it into a rule:
rules:
- name: wasm-injection-scan
priority: 1
description: "Fast pattern-based injection detection"
condition:
wasm_plugin: "injection-detector"
wasm_function: "evaluate"
action:
type: suppress
reason: "Prompt injection pattern detected by WASM scanner"
See WASM Plugins for the full plugin development guide, including resource limit configuration and module import validation.
Why WASM for Security Checks?
Unlike sidecar processes or HTTP-based validators, WASM plugins execute in-process with zero network overhead and complete isolation. A plugin cannot read environment variables, access the host filesystem, or make outbound connections. If a plugin exceeds its CPU fuel budget, Acteon terminates it immediately and returns a safe default. This makes WASM ideal for running security-critical checks like injection detection in an agent swarm where the detection logic itself must be tamper-proof.
Layer 2: LLM Guardrail for Semantic Analysis¶
For attacks that evade pattern matching (paraphrased instructions, multi-turn manipulation), an LLM guardrail provides semantic analysis:
[llm_guardrail]
endpoint = "https://api.openai.com/v1/chat/completions"
model = "gpt-4"
api_key_env = "OPENAI_API_KEY"
policy = "block"
temperature = 0.0
max_tokens = 256
- name: llm-injection-check
priority: 2
description: "Semantic injection detection via LLM"
condition:
field: action.action_type
in: ["send_message", "execute_code", "call_api"]
action:
type: llm_guardrail
evaluator_name: "injection-detector"
block_on_flag: true
send_to: "security-review-queue"
Cost Considerations
LLM guardrails add latency and cost per evaluation. Use the WASM plugin layer to filter obvious attacks first, and only send surviving actions to the LLM. The three-level LLM policy resolution lets you configure different policies per action type.
Layer 3: Semantic Routing for Anomaly Detection¶
Semantic routing catches off-topic actions that slip through both layers. If a "researcher" agent suddenly starts dispatching actions that are semantically close to "infrastructure modification" or "credential access," the rule fires:
- name: detect-anomalous-research
priority: 3
description: "Flag researcher actions that drift into dangerous topics"
condition:
all:
- field: action.tenant
eq: "researcher-agent"
- semantic_match:
text_field: "action.payload.query"
topic: "infrastructure modification, credential access, or privilege escalation"
threshold: 0.75
action:
type: reroute
target_provider: "security-review-queue"
[embedding]
enabled = true
endpoint = "https://api.openai.com/v1/embeddings"
model = "text-embedding-3-small"
api_key_env = "OPENAI_API_KEY"
See Semantic Routing for embedding configuration and cache tuning.
4. Rate Limiting and Quotas¶
Runaway agents can exhaust API rate limits, burn through cloud budgets, or denial-of-service your own infrastructure. Acteon provides two complementary mechanisms: quota policies for hard per-tenant caps and throttle rules for per-action-type rate limits.
Quota Policies¶
Quota policies enforce a maximum number of actions per tenant per time window. They run in the pipeline before rule evaluation, so an over-quota agent is blocked regardless of which rules would have matched.
# Researcher agent: 500 actions per hour
[[quotas]]
id = "q-researcher-hourly"
namespace = "agent-swarm"
tenant = "researcher-agent"
max_actions = 500
window = "hourly"
overage_behavior = "block"
enabled = true
description = "Researcher agent hourly cap"
# Code agent: 200 actions per hour
[[quotas]]
id = "q-code-hourly"
namespace = "agent-swarm"
tenant = "code-agent"
max_actions = 200
window = "hourly"
overage_behavior = "block"
enabled = true
description = "Code agent hourly cap"
# Deploy agent: 50 actions per day (deployments are expensive)
[[quotas]]
id = "q-deploy-daily"
namespace = "agent-swarm"
tenant = "deploy-agent"
max_actions = 50
window = "daily"
overage_behavior = "block"
enabled = true
description = "Deploy agent daily cap"
| Agent | Window | Limit | Overage Behavior |
|---|---|---|---|
| researcher-agent | Hourly | 500 | Block |
| code-agent | Hourly | 200 | Block |
| deploy-agent | Daily | 50 | Block |
| support-agent | Hourly | 1000 | Warn (log only) |
See Tenant Quotas for the full API and available overage behaviors.
Throttle Rules¶
Throttle rules provide finer-grained rate limiting at the action-type level. Use them to prevent bursts even when the agent is under its quota:
rules:
- name: throttle-agent-api-calls
priority: 8
description: "Limit any agent to 10 external API calls per minute"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["call_api", "search_code", "query_database"]
action:
type: throttle
max_count: 10
window_seconds: 60
message: "Agent API call rate limit exceeded"
- name: throttle-agent-messages
priority: 8
description: "Limit outbound messages to 5 per minute per agent"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["send_email", "send_slack"]
action:
type: throttle
max_count: 5
window_seconds: 60
message: "Agent messaging rate limit exceeded"
See Throttling for details on window semantics and retry_after behavior.
5. Approval Gates for High-Risk Operations¶
Some agent actions are too dangerous to execute autonomously. Acteon's approval system holds these actions pending until a human reviewer approves or rejects them via HMAC-signed URLs.
sequenceDiagram
participant A as Deploy Agent
participant G as Acteon Gateway
participant S as State Store
participant H as Human Supervisor
A->>G: dispatch(deploy, env=production)
G->>G: Rule match: approve-deploy-production
G->>S: Create approval record
G-->>A: PendingApproval (approval_id, expires_at)
G->>H: Notification with approve/reject URLs
alt Approved
H->>G: POST /approve?sig=...
G->>G: Execute deployment
G-->>A: ActionOutcome::Executed
else Rejected
H->>G: POST /reject?sig=...
G-->>A: ActionOutcome::Rejected
else Expired (30 min)
Note over G,S: TTL expires
G->>S: Mark as expired
end Tiered Autonomy Rules¶
Define escalation tiers based on risk. Low-risk actions execute immediately, medium-risk actions are flagged, and high-risk actions require approval:
rules:
# ── Tier 1: Autonomous (low risk) ────────────────
- name: auto-approve-read-ops
priority: 5
description: "Read operations execute without approval"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["search_code", "read_file", "list_issues", "query_database"]
action:
type: allow
# ── Tier 2: Flagged (medium risk) ────────────────
- name: flag-write-ops
priority: 5
description: "Write operations are logged with extra detail"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["create_pr", "push_commit", "create_ticket"]
action:
type: allow
metadata:
risk_tier: "medium"
audit_detail: "full_payload"
# ── Tier 3: Approval required (high risk) ────────
- name: approve-destructive-ops
priority: 3
description: "Destructive operations require human approval"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["deploy", "delete_resource", "modify_permissions", "execute_code"]
action:
type: require_approval
message: "High-risk agent operation requires human approval"
ttl_seconds: 1800
# ── Tier 4: Always blocked ──────────────────────
- name: block-forbidden-ops
priority: 1
description: "Operations that agents must never perform"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["delete_database", "rotate_credentials", "modify_firewall"]
action:
type: suppress
reason: "Operation is permanently forbidden for agents"
See Human Approvals for approval URL signing, notification targets, and auto-approve conditions.
6. Multi-Agent Orchestration with Chains¶
Complex agent tasks often require coordinating multiple agents in sequence. Acteon's chain system orchestrates multi-step workflows where each step's output feeds into the next, with configurable failure policies and sub-chain composition.
Research-Summarize-Notify Pipeline¶
# Main pipeline: research → summarize → notify
[[chains]]
name = "research-pipeline"
on_failure = "abort"
timeout_seconds = 600
[[chains.steps]]
name = "search"
provider = "github"
action_type = "search_code"
[[chains.steps]]
name = "analyze"
provider = "llm"
action_type = "summarize"
delay_seconds = 2
[[chains.steps]]
name = "review"
sub_chain = "human-review-loop"
[[chains.steps]]
name = "notify"
provider = "slack"
action_type = "send_message"
# Sub-chain: human review with escalation
[[chains]]
name = "human-review-loop"
timeout_seconds = 300
[[chains.steps]]
name = "send-for-review"
provider = "slack"
action_type = "send_review_request"
[[chains.steps]]
name = "wait-approval"
provider = "internal"
action_type = "wait_approval"
on_failure = "dlq"
Trigger Rule¶
A rule triggers the chain when a research request arrives:
rules:
- name: trigger-research-pipeline
priority: 5
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
eq: "research_request"
action:
type: chain
chain_name: "research-pipeline"
Inspecting the DAG¶
Visualize the chain execution graph, including sub-chains:
# By chain execution ID
curl http://localhost:8080/v1/chains/chn-abc123/dag
# By chain definition name
curl http://localhost:8080/v1/chains/definitions/research-pipeline/dag
flowchart TB
subgraph research-pipeline
S1[search] --> S2[analyze]
S2 --> S3[review]
S3 --> S4[notify]
end
subgraph human-review-loop
R1[send-for-review] --> R2[wait-approval]
end
S3 -.->|sub-chain| R1
R2 -.->|result| S3 See Task Chains and Sub-Chains for payload templates, failure policies, and conditional branching.
7. Agent State Tracking¶
Track each agent's lifecycle through configurable state machines. This is useful for enforcing workflows (e.g., an agent must complete research before deploying) and for detecting stuck or runaway agents.
Agent Task State Machine¶
[[state_machines]]
name = "agent-task"
initial_state = "idle"
states = ["idle", "researching", "coding", "reviewing", "deploying", "completed", "stale"]
[[state_machines.transitions]]
from = "idle"
to = "researching"
[[state_machines.transitions]]
from = "researching"
to = "coding"
[[state_machines.transitions]]
from = "coding"
to = "reviewing"
[[state_machines.transitions]]
from = "reviewing"
to = "deploying"
[[state_machines.transitions]]
from = "deploying"
to = "completed"
# Any state can go back to idle
[[state_machines.transitions]]
from = "researching"
to = "idle"
[[state_machines.transitions]]
from = "coding"
to = "idle"
[[state_machines.transitions]]
from = "reviewing"
to = "idle"
# Timeout: agents stuck in any active state for 2 hours become stale
[[state_machines.timeouts]]
state = "researching"
after_seconds = 7200
transition_to = "stale"
[[state_machines.timeouts]]
state = "coding"
after_seconds = 7200
transition_to = "stale"
[[state_machines.timeouts]]
state = "deploying"
after_seconds = 3600
transition_to = "stale"
stateDiagram-v2
[*] --> idle
idle --> researching: start_research
researching --> coding: start_coding
coding --> reviewing: submit_review
reviewing --> deploying: approved
deploying --> completed: deploy_success
researching --> idle: cancel / timeout
coding --> idle: cancel / timeout
reviewing --> idle: cancel / timeout
researching --> stale: Timeout (2h)
coding --> stale: Timeout (2h)
deploying --> stale: Timeout (1h) Who Triggers Transitions?
Transitions happen when an action with the matching fingerprint is dispatched. The agent itself can dispatch a cancel action to abandon its current task, or an external supervisor can dispatch it on the agent's behalf. Timeouts (configured above) trigger automatically when an agent is stuck in a state for too long.
Suppressing Out-of-Order Actions¶
Use the state machine to enforce workflow order. For example, block a deploy attempt if the agent has not completed the review phase:
rules:
- name: block-deploy-without-review
priority: 2
description: "Agents must be in 'reviewing' state before deploying"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
eq: "deploy"
- field: state.current
not_eq: "reviewing"
action:
type: suppress
reason: "Cannot deploy without completing code review"
See State Machines for fingerprinting, timeout transitions, and notification triggers.
8. Failure Isolation¶
When an agent's target provider fails, circuit breakers prevent cascading failures across the swarm. Requests to the failing provider are rejected immediately (or rerouted to a fallback) until the provider recovers.
Circuit Breaker Configuration¶
[circuit_breaker]
enabled = true
failure_threshold = 5
success_threshold = 2
recovery_timeout_seconds = 60
# GitHub API: higher tolerance, webhook fallback
[circuit_breaker.providers.github]
failure_threshold = 10
recovery_timeout_seconds = 120
fallback_provider = "github-webhook-queue"
# Cloud deploy: lower tolerance, no fallback (fail fast)
[circuit_breaker.providers.cloud-deploy]
failure_threshold = 3
recovery_timeout_seconds = 300
Fallback Chain¶
Combine circuit breakers with chains for graceful degradation:
[[chains]]
name = "resilient-notify"
on_failure = "skip"
[[chains.steps]]
name = "try-slack"
provider = "slack"
action_type = "send_message"
on_failure = "skip"
[[chains.steps]]
name = "try-email"
provider = "email"
action_type = "send_message"
on_failure = "skip"
[[chains.steps]]
name = "try-webhook"
provider = "webhook"
action_type = "send_message"
on_failure = "dlq"
flowchart LR
A[Agent notification] --> B[Slack]
B -->|Circuit open| C[Email]
C -->|Circuit open| D[Webhook]
D -->|All failed| E[Dead Letter Queue]
B -->|Success| F[Done]
C -->|Success| F
D -->|Success| F See Circuit Breaker for state transitions, per-provider overrides, and health probes.
9. Deduplication and Idempotency¶
Agents frequently retry failed operations or re-execute tasks after restarts. Without deduplication, this leads to duplicate emails, duplicate PRs, or duplicate deployments.
Dedup Rules for Agent Actions¶
rules:
- name: dedup-agent-emails
priority: 15
description: "Deduplicate agent email sends within 10 minutes"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
eq: "send_email"
action:
type: deduplicate
ttl_seconds: 600
- name: dedup-agent-deploys
priority: 15
description: "Deduplicate deployment attempts within 1 hour"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
eq: "deploy"
action:
type: deduplicate
ttl_seconds: 3600
- name: dedup-agent-tickets
priority: 15
description: "Deduplicate ticket creation within 30 minutes"
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
eq: "create_ticket"
action:
type: deduplicate
ttl_seconds: 1800
Dedup Keys
Agents should set a meaningful dedup_key on each action. A good pattern is {agent_id}-{task_id}-{action_type} -- this prevents duplicates from the same agent on the same task while allowing different agents or tasks to execute the same action type independently. The task_id must be stable across agent restarts (e.g., a database-assigned ID or an externally provided job reference), otherwise a restarted agent will generate a new key and bypass deduplication.
See Deduplication for client-side dedup key strategies and TTL tuning.
10. Monitoring and Observability¶
Full visibility into agent activity is critical for debugging, compliance, and cost management. Acteon provides four observability surfaces: audit trails, real-time SSE streams, Prometheus metrics, and Grafana dashboards.
Audit Trail Queries¶
Query the audit trail to inspect agent activity:
# All actions by a specific agent in the last hour
curl "http://localhost:8080/v1/audit?namespace=agent-swarm&tenant=researcher-agent&since=1h"
# All suppressed actions (permission denials)
curl "http://localhost:8080/v1/audit?namespace=agent-swarm&outcome=suppressed&limit=50"
# All actions requiring approval
curl "http://localhost:8080/v1/audit?namespace=agent-swarm&outcome=pending_approval"
# Actions by a specific session across all agents
curl "http://localhost:8080/v1/audit?namespace=agent-swarm&metadata.session_id=sess-abc123"
Real-Time Event Stream¶
Subscribe to agent events via Server-Sent Events for real-time dashboards or supervisor alerting:
# Stream all agent-swarm events
curl -N http://localhost:8080/v1/events/stream?namespace=agent-swarm
# Stream events for a specific agent
curl -N "http://localhost:8080/v1/events/stream?namespace=agent-swarm&tenant=deploy-agent"
Each event arrives as an SSE message:
data: {"namespace":"agent-swarm","tenant":"deploy-agent","action_type":"deploy","outcome":"pending_approval","rule":"approve-deploy-production","timestamp":"2026-02-16T10:30:00Z"}
Prometheus Metrics¶
Acteon exports Prometheus metrics at GET /metrics/prometheus. Key metrics for agent swarm monitoring:
| Metric | Type | Description |
|---|---|---|
acteon_actions_dispatched_total | counter | Total dispatches across all agents |
acteon_actions_suppressed_total | counter | Actions blocked by permission rules |
acteon_actions_throttled_total | counter | Actions rejected by rate limits |
acteon_actions_deduplicated_total | counter | Duplicate actions filtered |
acteon_actions_pending_approval_total | counter | Actions waiting for human approval |
acteon_provider_success_rate | gauge | Per-provider success percentage |
acteon_provider_latency_p99_ms | gauge | Per-provider 99th percentile latency |
acteon_wasm_invocations_total | counter | WASM plugin evaluations (injection scans) |
acteon_wasm_errors_total | counter | WASM plugin failures |
acteon_circuit_breaker_state | gauge | Per-provider circuit state (0=closed, 1=open, 2=half-open) |
Grafana Dashboards¶
Use the pre-built Grafana dashboards to monitor agent swarm health:
The Acteon Overview dashboard shows:
- Total dispatches per minute (all agents combined)
- Outcome breakdown (executed vs. suppressed vs. throttled vs. approval)
- Top agents by dispatch volume
- Error rate and latency trends
The Provider Health dashboard shows:
- Per-provider success rate and latency percentiles
- Circuit breaker state history
- Fallback activation events
See Grafana Dashboards for custom panel configuration and alerting rules.
11. Integration with Claude Code¶
Claude Code is Anthropic's CLI-based coding agent. Its hooks system lets you intercept every tool call (file writes, bash commands, web fetches) and route them through Acteon for permission checks, injection scanning, and audit logging -- turning Claude Code into a governed agent in your swarm.
Architecture¶
sequenceDiagram
participant U as User
participant CC as Claude Code
participant H as PreToolUse Hook
participant A as Acteon Gateway
participant T as Tool (Bash/Write/Edit)
U->>CC: "deploy to staging"
CC->>CC: Plans Bash tool call
CC->>H: PreToolUse event (tool_name, tool_input)
H->>A: POST /v1/dispatch (action)
A->>A: Rules + WASM + LLM + Quotas
alt Allowed
A-->>H: outcome: executed
H-->>CC: exit 0 (allow)
CC->>T: Execute tool
else Suppressed
A-->>H: outcome: suppressed
H-->>CC: exit 2 (block)
CC->>CC: "Action blocked by policy"
else Pending Approval
A-->>H: outcome: pending_approval
H-->>CC: exit 2 (block, wait for approval)
end Every tool call Claude Code makes passes through the hook before execution. The hook dispatches the action to Acteon, which evaluates all the rules, quotas, and guardrails configured in this guide. The hook then allows or blocks the tool call based on the outcome.
Hook: Route Tool Calls Through Acteon¶
Create a hook script that intercepts tool calls and dispatches them to Acteon for evaluation:
#!/bin/bash
set -e
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')
# Map tool calls to Acteon action types
case "$TOOL_NAME" in
Bash)
ACTION_TYPE="execute_command"
;;
Write|Edit)
ACTION_TYPE="write_file"
;;
WebFetch|WebSearch)
ACTION_TYPE="web_access"
;;
Task)
ACTION_TYPE="spawn_agent"
;;
*)
# Allow non-sensitive tools (Read, Grep, Glob) without checking
exit 0
;;
esac
# Dispatch to Acteon for policy evaluation
RESPONSE=$(curl -s -X POST http://localhost:8080/v1/dispatch \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACTEON_AGENT_KEY" \
-d "{
\"namespace\": \"agent-swarm\",
\"tenant\": \"claude-code-agent\",
\"provider\": \"claude-code\",
\"action_type\": \"$ACTION_TYPE\",
\"payload\": $TOOL_INPUT,
\"metadata\": {
\"tool_name\": \"$TOOL_NAME\",
\"session_id\": \"$SESSION_ID\",
\"agent_role\": \"coding\"
},
\"dedup_key\": \"$SESSION_ID-$ACTION_TYPE-$(echo "$TOOL_INPUT" | md5sum | cut -d' ' -f1)\"
}" 2>/dev/null) || {
# If Acteon is unreachable, fail closed
echo "Acteon gateway unreachable -- blocking action for safety" >&2
exit 2
}
OUTCOME=$(echo "$RESPONSE" | jq -r '.outcome // "unknown"')
case "$OUTCOME" in
executed|deduplicated)
exit 0 # Allow the tool call
;;
pending_approval)
APPROVAL_ID=$(echo "$RESPONSE" | jq -r '.approval_id // "unknown"')
echo "Action held for human approval (ID: $APPROVAL_ID). Check your notification channel." >&2
exit 2
;;
*)
REASON=$(echo "$RESPONSE" | jq -r '.rule_name // "policy violation"')
echo "Blocked by Acteon: $REASON" >&2
exit 2
;;
esac
Register the hook in your project settings:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit|WebFetch|WebSearch|Task",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/acteon-gate.sh",
"timeout": 10,
"statusMessage": "Checking Acteon policy..."
}
]
}
]
}
}
Fail Closed
The hook script blocks the action if Acteon is unreachable. This is the safe default for agent swarms -- a network partition should not grant an agent more permissions than it normally has.
Hook: Scan Prompts for Injection Attacks¶
User prompts submitted to Claude Code can contain injection payloads designed to override the agent's instructions. A UserPromptSubmit hook scans the prompt through Acteon before Claude processes it:
#!/bin/bash
set -e
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // empty')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')
# Skip empty prompts
[ -z "$PROMPT" ] && exit 0
RESPONSE=$(curl -s -X POST http://localhost:8080/v1/dispatch \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACTEON_AGENT_KEY" \
-d "{
\"namespace\": \"agent-swarm\",
\"tenant\": \"claude-code-agent\",
\"provider\": \"claude-code\",
\"action_type\": \"process_prompt\",
\"payload\": {\"prompt\": $(echo "$PROMPT" | jq -Rs .)},
\"metadata\": {\"session_id\": \"$SESSION_ID\"}
}" 2>/dev/null) || exit 0 # Fail open for prompts to avoid blocking the user
OUTCOME=$(echo "$RESPONSE" | jq -r '.outcome // "executed"')
if [ "$OUTCOME" = "suppressed" ]; then
REASON=$(echo "$RESPONSE" | jq -r '.rule_name // "injection detected"')
echo "Prompt blocked: $REASON" >&2
exit 2
fi
exit 0
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prompt-scan.sh",
"timeout": 5,
"statusMessage": "Scanning prompt..."
}
]
}
]
}
}
Acteon Rules for Claude Code¶
Add rules that match on the claude-code-agent tenant to control what Claude Code can do:
rules:
# ── Block dangerous shell commands ───────────────
- name: block-destructive-commands
priority: 1
description: "Block rm -rf, DROP TABLE, and other destructive commands"
condition:
all:
- field: action.tenant
eq: "claude-code-agent"
- field: action.action_type
eq: "execute_command"
- field: action.payload.command
matches: "(rm\\s+-rf|DROP\\s+TABLE|mkfs|dd\\s+if=|:\\(\\)\\{|chmod\\s+-R\\s+777)"
action:
type: suppress
reason: "Destructive shell command blocked"
# ── Block writes to sensitive paths ──────────────
- name: block-sensitive-file-writes
priority: 1
description: "Prevent writing to credentials, env files, or CI config"
condition:
all:
- field: action.tenant
eq: "claude-code-agent"
- field: action.action_type
eq: "write_file"
- field: action.payload.file_path
matches: "(\\.env|\\.ssh|credentials|\\bci/|\\.github/workflows)"
action:
type: suppress
reason: "Write to sensitive path blocked"
# ── Require approval for production deploys ──────
- name: approve-claude-code-deploy
priority: 3
description: "Claude Code deploy commands need human approval"
condition:
all:
- field: action.tenant
eq: "claude-code-agent"
- field: action.action_type
eq: "execute_command"
- field: action.payload.command
matches: "(kubectl apply|terraform apply|docker push|git push)"
action:
type: require_approval
message: "Claude Code is attempting a deploy operation"
ttl_seconds: 600
# ── Scan prompts for injection via WASM ──────────
- name: scan-claude-code-prompts
priority: 1
description: "Scan user prompts for injection patterns"
condition:
all:
- field: action.tenant
eq: "claude-code-agent"
- field: action.action_type
eq: "process_prompt"
- wasm_plugin: "injection-detector"
wasm_function: "evaluate"
action:
type: suppress
reason: "Prompt injection detected"
# ── Throttle agent commands ──────────────────────
- name: throttle-claude-code-commands
priority: 8
description: "Limit Claude Code to 30 commands per minute"
condition:
all:
- field: action.tenant
eq: "claude-code-agent"
- field: action.action_type
eq: "execute_command"
action:
type: throttle
max_count: 30
window_seconds: 60
# ── Allow read-only operations ───────────────────
- name: allow-claude-code-reads
priority: 10
condition:
all:
- field: action.tenant
eq: "claude-code-agent"
- field: action.action_type
in: ["read_file", "search_code", "web_access"]
action:
type: allow
# ── Allow normal writes and commands ─────────────
- name: allow-claude-code-writes
priority: 15
condition:
all:
- field: action.tenant
eq: "claude-code-agent"
- field: action.action_type
in: ["write_file", "execute_command", "spawn_agent"]
action:
type: allow
CLAUDE.md Behavioral Constraints¶
Combine Acteon enforcement with CLAUDE.md instructions so the agent is guided by policy even before hooks fire:
## Agent Safety Policy
All tool calls in this project are routed through the Acteon gateway for
policy enforcement. Do not attempt to bypass or work around blocked actions.
If an action is blocked:
1. Report the block reason to the user
2. Suggest an alternative approach that stays within policy
3. Do not retry the same blocked action
Constraints:
- Never write to .env, .ssh, or credential files
- Never run rm -rf, DROP TABLE, or filesystem format commands
- Always use the project's deploy pipeline -- never run kubectl/terraform directly
- All production operations require human approval through Acteon
Coding Agent Attack Vectors¶
Coding agents face unique attack vectors beyond standard prompt injection. The table below maps each vector to the Acteon defense layer that mitigates it:
| Attack Vector | Description | Acteon Defense |
|---|---|---|
| Prompt injection in code comments | Malicious instructions hidden in source files the agent reads | WASM plugin scans process_prompt payloads; LLM guardrail for semantic analysis |
| Dependency confusion | Agent installs a malicious package via npm install or pip install | PreToolUse hook dispatches execute_command to Acteon; regex rule blocks unknown registries |
| Credential exfiltration | Agent cats .env then sends contents via curl | Rule blocks writes/reads to credential paths; throttle limits outbound network calls |
| Privilege escalation | Agent runs sudo, chmod 777, or modifies CI pipelines | Regex rule on execute_command blocks dangerous patterns; writes to .github/workflows suppressed |
| Hidden file modification | Agent writes to dotfiles (.bashrc, .gitconfig) to persist access | write_file rule blocks dotfile paths; audit trail records all write attempts |
| Supply chain via subagents | A Task tool spawns a subagent that bypasses the parent's hooks | Hook matches Task tool and dispatches spawn_agent to Acteon for policy check |
| Multi-turn manipulation | Attacker gradually shifts the agent's behavior across many prompts | UserPromptSubmit hook scans each prompt; semantic routing detects topic drift |
| Resource exhaustion | Agent spawns infinite loops or fills disk with large files | Quota policy caps total actions per hour; throttle limits command frequency |
Defense in Depth
No single layer catches everything. The PreToolUse hook catches tool-level attacks, the UserPromptSubmit hook catches prompt-level attacks, and CLAUDE.md provides behavioral guidance that reduces the attack surface before any hook fires. Combined with Acteon's rule engine, WASM plugins, and LLM guardrails, you get overlapping coverage across all vectors.
12. Comparison: Claude Code + Acteon vs. OpenClaw vs. Manus¶
The AI agent landscape in 2026 is dominated by two platforms: OpenClaw, the open-source autonomous agent that went viral with 160k+ GitHub stars, and Manus, the closed-source multi-agent platform acquired by Meta. Both can run multi-agent swarms, but neither provides the kind of centralized, configurable safety layer that production deployments require.
Can you build a governed agent swarm with Claude Code + Acteon that matches or exceeds what OpenClaw and Manus offer? The short answer: yes -- and with significantly stronger safety guarantees.
Architecture Comparison¶
flowchart TB
subgraph OpenClaw
OC_GW[Gateway<br/>WebSocket hub]
OC_AG[Agent Runtime<br/>single Node process]
OC_SB[Docker Sandbox<br/>opt-in]
OC_GW --> OC_AG --> OC_SB
end
subgraph Manus
MA_PL[Planner Agent]
MA_EX[Executor Agents<br/>cloud VMs]
MA_SB[Session Sandbox<br/>cloud-only]
MA_PL --> MA_EX --> MA_SB
end
subgraph Claude Code + Acteon
CC[Claude Code<br/>local agent]
HK[Hooks<br/>PreToolUse / UserPromptSubmit]
AG[Acteon Gateway<br/>rule engine + WASM + LLM]
PR[Providers<br/>any target]
CC --> HK --> AG --> PR
end Feature Matrix¶
| Capability | OpenClaw | Manus | Claude Code + Acteon |
|---|---|---|---|
| Agent runtime | Single Node.js process, Agent SDK swarms | Cloud VMs, multi-agent planner/executor | Claude Code (local), Claude Agent SDK for swarms |
| Sandboxing | Docker containers (opt-in via NanoClaw) | Cloud VM per session (no self-host) | Acteon WASM sandbox (in-process, no FS/network) + optional Docker |
| Permission model | Layered policy: tool profiles, group policy, allowlists | Prompt-level guardrails, role-based page access | Acteon rule engine: deny-by-default capability matrix per agent |
| Prompt injection defense | Application-level -- model-last security posture | Prompt-level ethical guardrails | Three layers: WASM pattern scan + LLM semantic analysis + semantic routing |
| Rate limiting | No built-in per-agent quotas | No user-configurable rate limits | Per-tenant quotas + per-action-type throttle rules |
| Human approval gates | No built-in approval workflow | No built-in approval workflow | HMAC-signed approve/reject URLs with configurable TTLs |
| Action audit trail | Conversation logs, workspace files | Session history (cloud-only) | Structured audit with outcome tracking, payload storage, field redaction |
| Circuit breakers | No built-in provider failure isolation | Cloud infrastructure handles failover | Per-provider circuit breakers with fallback chains |
| Multi-step orchestration | Agent SDK tool chaining | Planner decomposes into sub-tasks | Chains with sub-chains, conditional branching, DAG visualization |
| State machines | No built-in workflow enforcement | No user-configurable state tracking | Configurable state machines with timeout transitions |
| Observability | Log files, basic metrics | Cloud dashboard (closed) | Prometheus metrics, Grafana dashboards, SSE streams, audit API |
| Deployment | Self-hosted (Node.js) or cloud | Cloud-only (no self-host) | Self-hosted (Rust binary), any infrastructure |
| Source model | Open source (ISC license) | Closed source | Acteon: open source (Apache 2.0), Claude Code: commercial |
What OpenClaw Gets Right (and Where Acteon Fills the Gaps)¶
OpenClaw's gateway architecture is conceptually similar to Acteon: a central hub that routes messages between channels and agents. Its layered security model (Identity -> Scope -> Model) is sound. However, there are critical gaps:
-
Sandboxing is opt-in. By default, agents run in the host Node.js process with full system access. NanoClaw fixes this with Docker containers, but it is a separate project and adds operational complexity. Acteon's WASM plugins run sandboxed by default -- no Docker required.
-
No configurable rule engine. OpenClaw's tool access is controlled by static policy files. There is no way to write conditional rules that match on payload content, action type, and agent identity simultaneously. Acteon's rule engine supports arbitrary conditions in YAML or CEL with priority-based evaluation.
-
No approval workflows. OpenClaw has no built-in mechanism to hold an action pending until a human approves it. With Acteon, any rule can gate an action behind HMAC-signed approval URLs.
-
No per-agent quotas. A runaway OpenClaw agent can exhaust API rate limits without any governor. Acteon enforces per-tenant quotas that block or warn before limits are reached.
What Manus Gets Right (and Where Acteon Fills the Gaps)¶
Manus excels at autonomous task execution. Its planner/executor architecture decomposes complex goals into sub-tasks and executes them in parallel cloud VMs. However:
-
Cloud-only, closed-source. Manus cannot be self-hosted. Your data leaves your network and enters a third-party cloud environment. Acteon runs on your own infrastructure with no external dependencies.
-
No user-configurable policy engine. Manus has prompt-level ethical guardrails and basic role access, but you cannot define custom rules that match on payload content or enforce per-agent rate limits. Acteon gives you a full rule engine, WASM plugins, and LLM guardrails.
-
No audit trail. Manus provides session history in its cloud dashboard, but there is no structured, queryable audit log with outcome tracking and field-level redaction. Acteon stores every dispatch with full metadata in PostgreSQL, ClickHouse, or Elasticsearch.
-
No failure isolation. When a Manus sub-agent's target service fails, there is no circuit breaker or fallback mechanism. Acteon's circuit breakers automatically stop requests to failing providers and optionally reroute to fallbacks.
Building a Production Agent Swarm¶
Here is how the three approaches compare for a concrete scenario: a four-agent swarm (researcher, coder, reviewer, deployer) working on a codebase.
| Concern | OpenClaw | Manus | Claude Code + Acteon |
|---|---|---|---|
| Agent isolation | Docker containers (NanoClaw) | Cloud VMs | Tenant isolation + WASM sandbox |
| "Coder can't deploy" | Tool profile policy file | Not configurable | Rule: tenant=code-agent + action_type=deploy -> suppress |
| "Deployer needs approval for prod" | Not supported | Not supported | Rule: require_approval with TTL and notification |
| "Researcher limited to 500 calls/hr" | Not supported | Not supported | Quota policy: max_actions=500, window=hourly |
| "Detect injection in code comments" | Model-last assumption | Prompt guardrails | WASM scan + LLM guardrail + semantic routing |
| "All actions auditable for 90 days" | Log files (unstructured) | Cloud dashboard | Structured audit with 90-day TTL and redaction |
| "Slack circuit breaker at 5 failures" | Not supported | Cloud infra | circuit_breaker.providers.slack.failure_threshold = 5 |
| "Visualize agent workflow" | Not built-in | Task tree (closed UI) | Chain DAG API + admin UI |
Not Mutually Exclusive
You can run OpenClaw or Manus agents through Acteon. The hook-based integration shown in Section 11 works with any agent that makes HTTP calls or runs shell commands. Acteon does not replace the agent runtime -- it governs the actions the runtime produces. You could run an OpenClaw swarm with every agent action dispatched through Acteon for policy enforcement, quotas, and audit.
When to Use Each¶
-
OpenClaw is the right choice when you need a messaging-first agent that connects to WhatsApp, Slack, or iMessage and runs tasks triggered by chat messages. Pair it with Acteon for safety.
-
Manus is the right choice when you need zero-setup autonomous task execution and are comfortable with cloud-only deployment and limited configurability. Not suitable for regulated environments.
-
Claude Code + Acteon is the right choice when you need a governed coding agent swarm with fine-grained permissions, prompt injection defense, approval workflows, full audit trails, and self-hosted deployment. This is the only option that gives you a configurable policy engine between the agent and the outside world.
13. Complete Configuration¶
Below is the full configuration combining all sections of this guide into a single deployment.
# ─── Server ─────────────────────────────────────────────
[server]
host = "0.0.0.0"
port = 8080
# ─── Authentication ─────────────────────────────────────
[auth]
enabled = true
config_path = "auth.toml"
watch = true
# ─── State Backend ──────────────────────────────────────
[state]
backend = "redis"
url = "redis://localhost:6379"
# ─── Audit ──────────────────────────────────────────────
[audit]
enabled = true
backend = "postgres"
url = "postgres://acteon:acteon@localhost:5432/acteon"
store_payload = true
ttl_seconds = 7776000 # 90 days
[audit.redact]
enabled = true
fields = ["api_key", "authorization"]
placeholder = "[REDACTED]"
# ─── LLM Guardrail ─────────────────────────────────────
[llm_guardrail]
endpoint = "https://api.openai.com/v1/chat/completions"
model = "gpt-4"
api_key_env = "OPENAI_API_KEY"
policy = "block"
temperature = 0.0
max_tokens = 256
# ─── Embedding (Semantic Routing) ──────────────────────
[embedding]
enabled = true
endpoint = "https://api.openai.com/v1/embeddings"
model = "text-embedding-3-small"
api_key_env = "OPENAI_API_KEY"
# ─── WASM Plugins (sandboxed: no FS, no network) ─────
[wasm]
enabled = true
module_dir = "./plugins"
memory_limit_bytes = 16777216 # 16 MB per invocation
cpu_timeout_ms = 100 # Hard kill after 100ms
# ─── Circuit Breaker ────────────────────────────────────
[circuit_breaker]
enabled = true
failure_threshold = 5
success_threshold = 2
recovery_timeout_seconds = 60
[circuit_breaker.providers.github]
failure_threshold = 10
recovery_timeout_seconds = 120
fallback_provider = "github-webhook-queue"
[circuit_breaker.providers.cloud-deploy]
failure_threshold = 3
recovery_timeout_seconds = 300
# ─── Quotas ─────────────────────────────────────────────
[[quotas]]
id = "q-researcher-hourly"
namespace = "agent-swarm"
tenant = "researcher-agent"
max_actions = 500
window = "hourly"
overage_behavior = "block"
enabled = true
[[quotas]]
id = "q-code-hourly"
namespace = "agent-swarm"
tenant = "code-agent"
max_actions = 200
window = "hourly"
overage_behavior = "block"
enabled = true
[[quotas]]
id = "q-deploy-daily"
namespace = "agent-swarm"
tenant = "deploy-agent"
max_actions = 50
window = "daily"
overage_behavior = "block"
enabled = true
# ─── State Machine ─────────────────────────────────────
[[state_machines]]
name = "agent-task"
initial_state = "idle"
states = ["idle", "researching", "coding", "reviewing", "deploying", "completed", "stale"]
[[state_machines.transitions]]
from = "idle"
to = "researching"
[[state_machines.transitions]]
from = "researching"
to = "coding"
[[state_machines.transitions]]
from = "coding"
to = "reviewing"
[[state_machines.transitions]]
from = "reviewing"
to = "deploying"
[[state_machines.transitions]]
from = "deploying"
to = "completed"
[[state_machines.transitions]]
from = "researching"
to = "idle"
[[state_machines.transitions]]
from = "coding"
to = "idle"
[[state_machines.transitions]]
from = "reviewing"
to = "idle"
[[state_machines.timeouts]]
state = "researching"
after_seconds = 7200
transition_to = "stale"
[[state_machines.timeouts]]
state = "coding"
after_seconds = 7200
transition_to = "stale"
[[state_machines.timeouts]]
state = "deploying"
after_seconds = 3600
transition_to = "stale"
# ─── Chains ─────────────────────────────────────────────
[[chains]]
name = "research-pipeline"
on_failure = "abort"
timeout_seconds = 600
[[chains.steps]]
name = "search"
provider = "github"
action_type = "search_code"
[[chains.steps]]
name = "analyze"
provider = "llm"
action_type = "summarize"
delay_seconds = 2
[[chains.steps]]
name = "review"
sub_chain = "human-review-loop"
[[chains.steps]]
name = "notify"
provider = "slack"
action_type = "send_message"
[[chains]]
name = "human-review-loop"
timeout_seconds = 300
[[chains.steps]]
name = "send-for-review"
provider = "slack"
action_type = "send_review_request"
[[chains.steps]]
name = "wait-approval"
provider = "internal"
action_type = "wait_approval"
on_failure = "dlq"
[[chains]]
name = "resilient-notify"
on_failure = "skip"
[[chains.steps]]
name = "try-slack"
provider = "slack"
action_type = "send_message"
on_failure = "skip"
[[chains.steps]]
name = "try-email"
provider = "email"
action_type = "send_message"
on_failure = "skip"
[[chains.steps]]
name = "try-webhook"
provider = "webhook"
action_type = "send_message"
on_failure = "dlq"
rules:
# ── Permanently blocked operations ───────────────
- name: block-forbidden-ops
priority: 1
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["delete_database", "rotate_credentials", "modify_firewall"]
action:
type: suppress
reason: "Operation is permanently forbidden for agents"
# ── Injection defense: WASM ──────────────────────
- name: wasm-injection-scan
priority: 1
condition:
wasm_plugin: "injection-detector"
wasm_function: "evaluate"
action:
type: suppress
reason: "Prompt injection pattern detected"
# ── Injection defense: LLM ───────────────────────
- name: llm-injection-check
priority: 2
condition:
field: action.action_type
in: ["send_message", "execute_code", "call_api"]
action:
type: llm_guardrail
evaluator_name: "injection-detector"
block_on_flag: true
send_to: "security-review-queue"
# ── Injection defense: semantic anomaly ──────────
- name: detect-anomalous-research
priority: 3
condition:
all:
- field: action.tenant
eq: "researcher-agent"
- semantic_match:
text_field: "action.payload.query"
topic: "infrastructure modification, credential access, or privilege escalation"
threshold: 0.75
action:
type: reroute
target_provider: "security-review-queue"
# ── Approval gates ───────────────────────────────
- name: approve-destructive-ops
priority: 3
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["deploy", "delete_resource", "modify_permissions", "execute_code"]
action:
type: require_approval
message: "High-risk agent operation requires human approval"
ttl_seconds: 1800
# ── Throttle rules ──────────────────────────────
- name: throttle-agent-api-calls
priority: 8
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
in: ["call_api", "search_code", "query_database"]
action:
type: throttle
max_count: 10
window_seconds: 60
# ── Agent permissions ────────────────────────────
- name: allow-researcher-search
priority: 10
condition:
all:
- field: action.tenant
eq: "researcher-agent"
- field: action.action_type
in: ["search_code", "read_file", "list_issues"]
action:
type: allow
- name: allow-code-agent-pr
priority: 10
condition:
all:
- field: action.tenant
eq: "code-agent"
- field: action.action_type
in: ["create_branch", "create_pr", "push_commit"]
action:
type: allow
- name: allow-deploy-staging
priority: 10
condition:
all:
- field: action.tenant
eq: "deploy-agent"
- field: action.action_type
eq: "deploy"
- field: action.payload.environment
eq: "staging"
action:
type: allow
- name: allow-support-messaging
priority: 10
condition:
all:
- field: action.tenant
eq: "support-agent"
- field: action.action_type
in: ["send_email", "send_slack", "create_ticket"]
action:
type: allow
# ── Deduplication ────────────────────────────────
- name: dedup-agent-emails
priority: 15
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
eq: "send_email"
action:
type: deduplicate
ttl_seconds: 600
- name: dedup-agent-deploys
priority: 15
condition:
all:
- field: action.namespace
eq: "agent-swarm"
- field: action.action_type
eq: "deploy"
action:
type: deduplicate
ttl_seconds: 3600
# ── Catch-all deny ──────────────────────────────
- name: deny-all-agents
priority: 100
condition:
field: action.namespace
eq: "agent-swarm"
action:
type: suppress
reason: "No matching permission rule"
14. Best Practices¶
-
Deny by default. Start with a catch-all suppress rule and explicitly allow only the operations each agent needs. This prevents privilege creep as you add new action types.
-
Use tenants for isolation. Each agent (or agent class) should have its own tenant. This gives you per-agent audit trails, quotas, and rules without additional configuration.
-
Layer your defenses. No single check catches everything. Combine WASM pattern scanning (fast, cheap) with LLM semantic analysis (thorough, expensive) and semantic routing (anomaly detection).
-
Set meaningful dedup keys. Use
{agent_id}-{task_id}-{action_type}to prevent duplicate actions from retries while allowing different agents or tasks to perform the same operation. Ensuretask_idis stable across agent restarts. -
Gate destructive operations. Any action that modifies production infrastructure, deletes data, or changes permissions should require human approval. Use tiered autonomy (Section 5) to avoid bottlenecking read-only operations.
-
Monitor suppression rates. A spike in suppressed actions often indicates a misconfigured agent or a prompt injection attack. Set Grafana alerts on
acteon_actions_suppressed_total. -
Use state machines for workflow enforcement. Agents that must follow a prescribed workflow (research -> code -> review -> deploy) should be tracked through a state machine. This prevents agents from skipping steps.
-
Set quotas conservatively. Start with low limits and increase based on observed usage. It is easier to raise a quota than to recover from a runaway agent that burned through your cloud budget.
-
Test rules in the playground. Before deploying rule changes, use the Rule Playground to verify that each agent can perform its intended operations and is blocked from everything else.
-
Audit everything. Enable
store_payload = truewith field redaction. Full payload audit trails are invaluable for post-incident analysis of agent misbehavior.
15. See Also¶
- Runnable Example -- Single-agent and 3-agent swarm demos with hooks, rules, and PostgreSQL
- Rule System -- How rules are evaluated and matched
- The Dispatch Pipeline -- Full pipeline stages
- Authentication -- API key and JWT configuration
- WASM Plugins -- Custom plugin development
- LLM Guardrails -- LLM-based content gates
- Semantic Routing -- Embedding-based routing
- Tenant Quotas -- Per-tenant rate limits
- Throttling -- Per-action-type rate limits
- Human Approvals -- Approval workflows
- Task Chains -- Multi-step orchestration
- Sub-Chains -- Chain composition
- State Machines -- Event lifecycle tracking
- Circuit Breaker -- Failure isolation
- Deduplication -- Idempotent action handling
- Audit Trail -- Comprehensive event logging
- Grafana Dashboards -- Pre-built dashboards
- Rule Playground -- Interactive rule testing