ADR-008: Agent Process Architecture

Why Owlat uses a process-oriented architecture for the agent pipeline instead of a single sequential function.

  • Status: Accepted
  • Date: 2026-03-24

Context

The Agent Pipeline processes inbound messages through five steps: context retrieval, classification, action planning, draft generation, and routing. The simplest implementation is a single function that runs all five steps sequentially. But this creates problems:

  1. No partial retry — if draft generation fails (LLM timeout, rate limit), the entire pipeline restarts from context retrieval, wasting LLM calls
  2. No concurrency — one long-running pipeline blocks the next message from starting. A 10-second draft generation delays all subsequent messages
  3. No multi-intent handling — a single message asking about billing and requesting a feature needs two separate processing paths
  4. No observability — a monolithic function provides one timing metric, not per-step breakdown

Convex's serverless model naturally supports independent function execution — each internalAction runs independently, can be scheduled, retried, and monitored separately.

Decision

Adopt a process-oriented architecture with three process types mapped to Convex primitives:

ProcessConvex PrimitivePipeline StepsBehavior
ReceiverHTTP actionInbound webhookStateless — stores message, triggers security filter
AnalyzerinternalActionSteps 1–2Context retrieval + classification. Can fork for multi-intent messages
WorkerinternalActionSteps 3–5Autonomous execution with state machine (running / waiting_for_input / done / failed)

A coordinator mutation tracks the pipeline state in the inboundMessages table and schedules the next step when the previous one completes. Each step records its timing and result independently.

Multi-intent branching

When classification detects multiple intents, the coordinator spawns parallel worker processes — one per intent. Each worker runs independently with its own context, action plan, and draft. The routing step merges results when appropriate.

Retry semantics

Each process type has independent retry behavior:

  • Receiver — no retry (webhook delivery is the provider's responsibility)
  • Analyzer — retry up to 2 times with exponential backoff. If classification repeatedly fails, mark the message for human review
  • Worker — retry up to 3 times. On persistent failure, save partial progress (the action plan) and route to verification queue with a "needs manual intervention" flag

Consequences

Enables:

  • Independent retry per step — a failed draft generation retries only the draft, not the full pipeline
  • Concurrent processing — multiple messages process simultaneously across organizations
  • Per-step observability — timing, cost, and error metrics per pipeline step
  • Multi-intent handling — parallel worker branches for complex messages
  • Graceful degradation — partial pipeline results are still useful (a classification without a draft is better than nothing)

Trade-offs:

  • More schema fields for state tracking (processingState, retryCount, stepTimings)
  • Coordinator logic adds complexity vs a simple sequential function
  • State machine transitions need careful testing to avoid stuck messages
  • Slightly higher latency than a single function call due to scheduling overhead (~50ms per step transition)