Routing Compliance Tasks to Regional Legal-Ops Teams
This guide is part of the Registered Agent Assignment Logic area within the Deadline Tracking & Routing Engines framework: it drills into one sub-problem of assignment — given a stream of already-validated filing tasks, deciding which regional legal-ops team owns each one and delivering it to that team’s queue without ever stranding a statutory deadline.
Scope of This Page
This page covers the routing layer only: how to map a task’s jurisdiction onto an owning regional queue, how to fall back deterministically when the primary queue is saturated or unreachable, and how to record every routing decision in an append-only audit trail. It deliberately excludes adjacent machinery documented elsewhere — obligation scoring and admission ordering, the concurrency model for dispatching to portals, the schema validation that admits a task in the first place, and the agent-of-record assignment itself. We assume each incoming task is single-intent, schema-valid, and already carries its jurisdiction code; here we decide who works it and prove where it went.
The Constraint That Forces Regional Routing
Regional routing is not an org-chart nicety — it is driven by where work may lawfully and practically be performed. Statements of Information filed under Cal. Corp. Code § 1502 and Delaware franchise-tax filings under Del. Code Ann. tit. 8, § 502 carry distinct deadlines, portal hours, and escalation paths, and the legal-ops staff who hold portal credentials and case knowledge for each registry are organized by region. A task routed to the wrong team is not merely inefficient: it can miss a same-day Delaware settlement window or a New York biennial cutoff (N.Y. Bus. Corp. Law § 408) while it waits in a queue nobody who can act is watching. Routing therefore has to be deterministic (the same task always reaches the same owner), idempotent (a retried route never double-enqueues), and auditable (every assignment is provable after the fact for reconciliation).
Prerequisites
- Python 3.10+ — for
X | Yunions,matchstatements, and modernasyncio. - Standard library only:
asyncio,dataclasses,enum,hashlib,json,logging,time. - A queue client per region (SQS, Redis Streams, RabbitMQ, or an internal task bus) exposing an async
push(queue, task)and adepth(queue)health probe. - A routing table mapping jurisdiction codes to a primary region and an ordered fallback chain, kept as configuration data — not branches in code.
- An append-only audit sink (write-once table or immutable-retention log stream) for routing decisions.
- A stream of pre-validated, single-intent task records carrying
entity_id,jurisdiction, andstatutory_deadline(see the parent Registered Agent Assignment Logic task model).
Implementation: A Deterministic Regional Router
The module below resolves each task to its owning region from a configuration table, probes that region’s queue depth before committing, and walks an ordered fallback chain when the primary is saturated or unreachable. Every route is keyed by a stable routing_hash so a retried task lands on the same queue rather than fanning out, and every decision is hashed into a tamper-evident audit record. Comments mark the compliance-critical lines.
from __future__ import annotations
import asyncio
import hashlib
import json
import logging
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import AsyncGenerator, Optional, Protocol
# Structured JSON logging — every line is a parseable audit/observability event.
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger("routing.regional_dispatch")
class RouteStatus(Enum):
ROUTED = "routed" # placed on a regional queue (primary or fallback)
ESCALATED = "escalated" # fallback chain exhausted — sent to human review
class QueueClient(Protocol):
async def push(self, queue: str, task: "ComplianceTask") -> None: ...
async def depth(self, queue: str) -> int: ...
@dataclass(frozen=True)
class RegionRoute:
jurisdiction: str # e.g. "US-DE"
primary: str # owning regional queue
fallbacks: tuple[str, ...] # ordered chain tried when primary is unavailable
depth_ceiling: int # backpressure threshold for the primary queue
@dataclass
class ComplianceTask:
entity_id: str
jurisdiction: str
statutory_deadline: float # Unix timestamp
routing_hash: str = ""
assigned_queue: Optional[str] = None
fallback_depth: int = 0
def __post_init__(self) -> None:
# Stable hash over identity + deadline: the same task always routes the
# same way, so a retry is idempotent rather than a second enqueue.
if not self.routing_hash:
payload = f"{self.entity_id}:{self.jurisdiction}:{self.statutory_deadline}"
self.routing_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
@dataclass(frozen=True)
class RoutingRecord:
routing_hash: str
entity_id: str
jurisdiction: str
status: RouteStatus
queue: str
fallback_depth: int
timestamp: float
integrity_hash: str = field(default="")
def sealed(self) -> "RoutingRecord":
# Tamper-evident seal over everything but the seal field itself.
body = {k: v for k, v in self.__dict__.items() if k != "integrity_hash"}
body["status"] = self.status.value
canonical = json.dumps(body, sort_keys=True, separators=(",", ":"))
digest = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
return RoutingRecord(**{**body, "status": self.status, "integrity_hash": digest})
class RegionalRouter:
ESCALATION_QUEUE = "global-compliance-escalation"
def __init__(self, table: dict[str, RegionRoute], client: QueueClient) -> None:
self.table = table
self.client = client
async def _try_queue(self, queue: str, ceiling: int, task: ComplianceTask) -> bool:
# Probe backpressure before committing: a saturated queue would silently
# delay a statutory deadline, so we treat it as unavailable and fall back.
try:
if await self.client.depth(queue) >= ceiling:
return False
await asyncio.wait_for(self.client.push(queue, task), timeout=5.0)
return True
except (asyncio.TimeoutError, ConnectionError) as exc:
logger.warning(json.dumps({"event": "queue_unavailable", "queue": queue,
"entity_id": task.entity_id, "error": str(exc)}))
return False
async def route(self, task: ComplianceTask) -> RoutingRecord:
route = self.table.get(task.jurisdiction)
if route is None: # unknown jurisdiction is a data defect, not a routing target
return self._seal(task, RouteStatus.ESCALATED, self.ESCALATION_QUEUE)
# Primary first, then each fallback in declared order — deterministic chain.
candidates = (route.primary, *route.fallbacks)
for depth, queue in enumerate(candidates):
ceiling = route.depth_ceiling if depth == 0 else route.depth_ceiling * 2
if await self._try_queue(queue, ceiling, task):
task.assigned_queue, task.fallback_depth = queue, depth
return self._seal(task, RouteStatus.ROUTED, queue)
# Chain exhausted: never drop a deadline — hand to supervised escalation.
await self.client.push(self.ESCALATION_QUEUE, task)
task.fallback_depth = len(candidates)
return self._seal(task, RouteStatus.ESCALATED, self.ESCALATION_QUEUE)
def _seal(self, task: ComplianceTask, status: RouteStatus, queue: str) -> RoutingRecord:
record = RoutingRecord(task.routing_hash, task.entity_id, task.jurisdiction,
status, queue, task.fallback_depth, time.time()).sealed()
logger.info(json.dumps({
"level": "WARNING" if status is RouteStatus.ESCALATED else "INFO",
"event": "task_routed", "routing_hash": record.routing_hash,
"entity_id": record.entity_id, "jurisdiction": record.jurisdiction,
"status": record.status.value, "queue": record.queue,
"fallback_depth": record.fallback_depth, "seal": record.integrity_hash,
}))
return record
async def run(self, source: AsyncGenerator[ComplianceTask, None]) -> list[RoutingRecord]:
return [await self.route(task) async for task in source]
The router is deliberately a leaf: it makes no priority decisions and performs no portal submission. It consumes single-intent tasks and returns one sealed RoutingRecord per task — routed or escalated — leaving execution to the owning regional team and reconciliation to the surrounding engine.
Configuration Reference
Routing targets, ceilings, and fallback order are configuration data, never branches in the routing loop, because they are dictated by team coverage and registry behavior rather than by your code.
| Parameter | Suggested value | Legal / operational justification |
|---|---|---|
primary (US-DE) |
us-east-legal-ops |
Delaware’s same-day settlement window (§ 502) demands a team in a timezone that can act before cutoff. |
primary (US-CA) |
us-west-legal-ops |
BizFile § 1502 work runs in California business hours (08:00–17:00 PT); route to staff in that window. |
primary (US-NY) |
us-east-legal-ops |
NY biennial filings (§ 408) share East-coast coverage and escalation paths with Delaware. |
primary (US-TX) |
us-central-legal-ops |
Texas Comptroller May 15 Public Information Reports are owned by the central region. |
depth_ceiling |
250 | Backpressure threshold per regional queue; above it, fall back rather than let a deadline wait. |
| Fallback ceiling multiplier | 2x |
Fallback queues absorb overflow at a looser ceiling so the chain does not escalate prematurely. |
push timeout |
5s | Fails fast on an unreachable queue so the next fallback is tried within the deadline budget. |
routing_hash length |
16 hex | Stable idempotency key per task; collisions are negligible at portfolio scale and keep logs compact. |
Failure Modes and Fallback Routing
The router maps each fault onto the four-tier scheme defined in the portal area’s error categorization & retry logic — transient, statutory, data-validation, and system — and responds to each differently.
- Primary queue saturated (transient/backpressure).
depth()reports the queue at or above its ceiling. This is a pacing signal, not a failure: the router advances to the next region in the declared chain at a looser ceiling. It never blocks on the saturated queue, because a waiting Delaware task can miss its settlement window. - Queue push timeout or connection error (system). The broker is unreachable inside the 5-second budget. The router logs
queue_unavailable, treats the target as unavailable, and falls back. A region that returns sustained failures should be circuit-broken upstream so the chain stops re-probing a dead broker. - Unknown jurisdiction code (data-validation). No routing-table entry exists for the task’s jurisdiction. This is a data defect, not a routing target: the task is sealed
ESCALATEDto the global review queue rather than guessed onto a regional team, and the upstream classifier is alerted to the bad code. - Fallback chain exhausted (statutory/escalation). Every candidate region is saturated or unreachable. The task is pushed to the supervised escalation queue with its
routing_hashand deadline intact, so a compliance officer can place it manually before the statutory cutoff. Nothing is ever silently dropped, and the idempotent hash means a later automated retry cannot double-enqueue it.
Frequently Asked Questions
Why probe queue depth before routing instead of just pushing to the primary?
Because a push that succeeds onto a saturated queue is the worst outcome: the task is “delivered” but sits behind hundreds of others while its statutory deadline elapses, and no error ever fires. Probing depth() against a per-region ceiling lets the router treat a backed-up queue as unavailable and fall back to a region with capacity, so a deadline is bounded by processing availability, not just delivery success.
What stops a retried task from being routed to two different teams?
The stable routing_hash, derived from the entity ID, jurisdiction, and statutory deadline and never regenerated. The router resolves the same candidate chain in the same order for an identical hash, and the regional queue client uses the hash as a dedup key, so a replayed task collapses onto the existing assignment rather than fanning out to a second team.
How is regional ownership kept correct when a deadline crosses timezones?
Ownership follows the registry, not the calendar: the routing table binds each jurisdiction to the region whose business hours overlap that portal’s filing window. A Delaware task always routes to the East-coast team regardless of where the portfolio’s headquarters sits, because that team holds the credentials and can act inside the § 502 settlement window. The table is the single source of truth, so a coverage change is a one-line config edit, not a code change.
What happens to a task whose jurisdiction has no routing-table entry?
It is never guessed onto a regional team. The router seals it ESCALATED to the global review queue and emits a WARNING-level structured log so the upstream classifier can be corrected. Routing to a plausible-but-wrong region would hide a data defect behind an apparently successful assignment, which is exactly the failure regional routing exists to prevent.