Pain Points → Cordis Remedies (Phase 0, Step 6)

Source: Audit P0–P10 (AppState god-struct through header usage tracking) + docs/cordis-mapping.md primitives. Rule: One-line remedy per pain point — the Cordis primitive that eliminates it.

IDPain Point (at e4f3bcc)Cordis Remedy
P0AppState god-struct (17–22 fields, src/lib.rs:230–274, clone cost, base_router wiring)Decomposed Context with typed provide/inject; handlers declare ctx.get::<T>() coeffects instead of receiving the whole state. type AppState = Arc<Context> shim for 1 commit, then State<Arc<Context>>.
P1cfg(feature) soup (6 #[cfg(postgres)] fields in ConfigurableAgent, #[cfg(mcp)], #[cfg(search-tools)] in handlers/main.rs)Feature-gated Service implementations registered via inventory/linkme (#[cfg(feature = "postgres")] only on the impl Service for PostgresService block); handlers use if ctx.get::<PostgresService>().is_some() / ctx.get::<LlmService>().check() not #[cfg] in bodies. Cargo.toml keeps feature flags for dep selection, business logic does not.
P2Duplicated agent execution (5 call-sites: chat.rs:execute_agent, v1.rs:v1_chat, scheduler.rs:execute_scheduled_agent, pipeline_engine.rs:execute_target_agent, trigger_engine.rs:execute_triggered_agent)Single AgentExecutionService::execute(req, ctx) that owns: history loading, memory/context_provider injection, ToolCoordinator loop, fallback LLM chain, observability sink (run_history + agent_runs), usage/cost/budget, loop detection, checkpointing. All 5 sites become ctx.get::<AgentExecutionService>().execute(...). Eliminates 5× drift.
P33-tier agent resolution triplication (resolve_agent_for_tenant / resolver.rs / registry.rs with tenant DB → community → system)AgentResolverService::resolve(name, tenant) -> Result<Arc<dyn Agent>> with ordered CoEffect providers; callers inject it once. Agents themselves become Services created by resolver, not by scattered AgentRegistry::create_agent. Supports ctx.isolate("agent", tenant_label) scoping.
P43 registries: ToolRegistry (static HashMap) / RuntimeToolRegistry (ArcSwap<HashMap> from DB) / McpRegistry (external MCP clients)Unified ToolService trait behind one ToolRegistry iface: resolve(name, tenant), list(tenant), reload(). Composes sources with precedence tenant runtime → fleet runtime → MCP bridge → static. Agents inject it; ctx.isolate("tool_service", tenant_label) enforces per-tenant visibility. Delete execute_for_tenant branching.
P5110 KB toml_config.rs (monolith, crates/ares-config/src/toml_config.rs + toon_config.rs)Split by domain (server, auth, providers, tools, agents, workflows, rag, billing) each as its own Service with schema; re-aggregated via Loader EntryTree. No silent API break — public API re-exported with #[deprecated] for one release if crate merged.
P6Sequential orchestrator (src/agents/orchestrator.rs runs subtasks serially)Parallel subtask execution via tokio::JoinSet behind same OrchestratorAgent interface. The interface is unchanged; internals swap for subtask in tasks { subtask.run() } for JoinSet::spawn + join_all with timeout/bail semantics from Dispatch::Parallel vs Serial.
P7Manual wiring (run_server 17 sequential steps in src/main.rs:296–889)Context::plugin(plugin, config) registration; run_server becomes root_ctx.plugin(...).await.plugin(...).await (5–8 lines). RegistryService tracks FiberId → Fiber and enforces single-source discipline ("duplicate provider for <TypeId>"). Static plugins via inventory/linkme, dev HMR behind #[cfg(feature = "hmr")].
P8MCP server HTTP loopback (MCP server calls reqwest to localhost:3000/api/chat to execute agents)Direct ctx.get::<AgentExecutionService>().execute(req, &ctx) — eliminate reqwest hop. MCP server becomes a Service that injects AgentExecutionService; latency improvement, no behavioral regression; proves Context is process-scoped, not HTTP-scoped.
P9No circuit breaker (ClientPool in crates/ares-llm/src/pool.rs has no Closed/Open/HalfOpen)Wrap ClientPool with breaker state as Service field (threshold, cooldown); Service::check() returns false when breaker Open, causing dependent fibers to deactivate gracefully (Thm 63 guarded withdrawal — provider does not withdraw until dependents Inactive). Expose health_metrics_job through breaker state.
P10Header-based usage tracking (X-Usage-* headers parsed ad-hoc in src/middleware/usage.rs + track_usage)Request-scoped UsageContext Service injected into handlers; middleware provides it per request (ctx.extend().provide(UsageContext::from_headers(req.headers()))), handlers inject it via ctx.get::<UsageContext>() and pass to AgentExecutionService for record_usage. No global mutation, testable in isolation.

Notes

  • P0–P10 numbering follows the audit that produced the plan; if audit re-runs show different IDs, keep this table's content and update IDs.
  • Each remedy is a Service decomposition (Phase 2–5) — no #[cfg] in handler bodies by Phase 6.
  • The Loader (EntryTree) is the mechanism that makes P5 and P7 composable declarative config rather than imperative wiring.
  • P8 and P9 are the smallest vertical slices to prove DI before tackling AppState decomposition (P0) — P8 validates AgentExecutionService directly, P9 validates Service::check() health.