Skip to main content

Agent Architecture

WASP is event-driven and Docker-based. The public install ships six services; place your own reverse proxy in front for TLS.

Service map

ServiceImageHost portRole
agent-redisredis:7-alpine(internal)Event bus (Streams), state cache (KV), session store
agent-postgrespostgres:16-alpine(internal)Durable storage — 28 tables
agent-corebuilt locally8080Agent runtime: events, LLM, skills, scheduler, dashboard, 151 HTTP endpoints
agent-telegrambuilt locally(none, long-polls Telegram)Telegram bridge ↔ Redis Streams
agent-brokerbuilt locally (root)(internal)Privileged Docker-API proxy with endpoint allowlist
agent-ollamaollama/ollama:latest(internal)Local LLM runtime (always present; no models pulled by default)

Only agent-core publishes a port to the host (8080). All other inter-service traffic stays on the private wasp-net Docker network.

All app containers run as non-root (UID 1000) except agent-broker, which needs root for Docker socket access. The broker enforces an allowlist of Docker API endpoints (/containers/*/start, /stop, /restart, /logs, /inspect, /list); other endpoints are blocked. See Privilege Boundaries.

:::info Operator agent-nginx The operator-controlled production deployment at agentwasp.com adds an agent-nginx container that terminates TLS and serves the landing page + docs. That container is not part of the public OSS install because it bakes in operator-specific SSL paths and server names. For your own setup, put your reverse proxy in front of port 8080. :::

Event-driven flow

User message (Telegram)


agent-telegram polls Telegram API

▼ XADD events:incoming
agent-redis (Streams)

▼ XREADGROUP (consumer group: agent-core)
agent-core EventBus.consume()


EventHandler.handle_message()

▼ (per-chat asyncio.Lock serializes concurrent messages)

▼ Pipeline (see below)

▼ XADD events:outgoing
agent-telegram XREADGROUP

▼ Telegram sendMessage API
User receives reply

The dashboard chat path runs the same pipeline but bypasses the streams — chat_direct() calls handle_message() in-process.

Request pipeline

Incoming message


1. Per-chat asyncio.Lock ─ serializes concurrent messages

2. Low-Intent Cold-Start Guard ─ clarification fast-path for
│ short/emoji/context-required input

3. auto_detect.py ─ 13 deterministic fast-paths
│ (Gmail inbox, reminder list/delete,
│ agent CRUD, YouTube search, etc.)

4. Decision Layer ─ heuristic classifier:
│ DIRECT_RESPONSE / GOAL /
│ SCHEDULED_TASK / SUB_AGENT / SCRIPT

5. Capability Engine ─ skipped if auto_detect already ran

6. Context Builder ─ injects:
│ prime.md, knowledge graph,
│ self-model, epistemic state,
│ temporal observations,
│ procedural memory hints,
│ behavioral rules,
│ episodic history,
│ vector-memory neighbors

7. LLM Loop (≤12 rounds) ─ skill parsing, parallel groups,
│ anticipatory simulation,
│ recovery memory consultation

8. Multi-URL Aggregator ─ deterministic per-URL outcome
│ when ≥2 browser URLs in batch

9. Response Validator ─ deterministic post-LLM check

10. Response Guard chain ─ schedule honesty, factual grounding,
│ markdown sanitizer

11. Action Announcer ─ strips unverified action claims,
│ surfaces hidden failures

12. handlers post-processing ─ final cleanup


Outgoing response

Each step writes to a DecisionTrace. Traces persist in Redis (TTL ~24 h) and are surfaced at /traces in the dashboard.

Telegram and dashboard paths

AspectTelegramDashboard
Entryagent-telegram polls Telegram API, publishes to Redisdashboard/routes/chat.py (HTTP POST)
AuthenticationTELEGRAM_ALLOWED_USERS allowlist (fail-closed; empty = bridge refuses to start)session cookie, DASHBOARD_SECRET, CSRF token bound to session
Concurrencyper-chat asyncio.Lockper-chat asyncio.Lock (shared)
Pipelineidenticalidentical
Streamingmessage-edit progress (TELEGRAM_PROGRESS)SSE via POST /chat/stream

Both paths converge at EventHandler.handle_message(), so a single regression suite covers both.

Per-chat lock and request budget

A per-chat asyncio.Lock ensures concurrent messages from the same chat are processed serially. This prevents race conditions in chat-scoped state (memory writes, flow lock, last-action tracker).

_REQUEST_BUDGET enforces a per-execution cap on LLM rounds based on request tier:

TierCap (rounds)
simple10
normal20
complex36

Tier is derived from _COMPLEX_MARKERS_RE in the user text (agent / daily / schedule / report / etc.). The budget is reset per request.

Failure recovery

FailureRecovery
LLM errorRetry with progressively shorter history
Skill errorError returned to LLM; can replan
DB errorGraceful degradation; in-memory fallback for self-model
Redis errorIn-memory fallback for non-essential state
Context overflowProgressive truncation in ModelManager.generate()
Pre-commit syntax error in self_improvePatch rejected with HTTP 400; no file written
Self-improve regressionTimestamped backup at /data/src_patches/backup_* allows rollback
Container crash mid-messagePEL zombie recovery at startup re-delivers idle messages
Goal replan stormGoal flipped to FAILED with partial output

See also