# 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.
| ID | Pain Point (at `e4f3bcc`) | Cordis Remedy |
|----|---------------------------|---------------|
| **P0** | `AppState` 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>>`. |
| **P1** | `cfg(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. |
| **P2** | Duplicated 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. |
| **P3** | 3-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 `Service`s created by resolver, not by scattered `AgentRegistry::create_agent`. Supports `ctx.isolate("agent", tenant_label)` scoping. |
| **P4** | 3 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. |
| **P5** | 110 KB `toml_config.rs` (monolith, `src/overlay.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. |
| **P6** | Sequential 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`. |
| **P7** | Manual 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")]`. |
| **P8** | MCP 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 `inject`s `AgentExecutionService`; latency improvement, no behavioral regression; proves Context is process-scoped, not HTTP-scoped. |
| **P9** | No 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. |
| **P10** | Header-based usage tracking (`X-Usage-*` headers parsed ad-hoc in `src/middleware/usage.rs` + `track_usage`) | Request-scoped `UsageContext` `Service` injected into handlers; middleware `provide`s 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.