Repository: alamparelli/alf
Author: alamparelli
**Goal**: unify the AI layer and introduce the single orchestrator.
Depends on: Steps 1–3 complete.
> **Note (2026-04-20)**: body amended after A3. The original text said "fuse provider + router + agents into `internal/ai/`". That is incompatible with §4 dependency rules: `router/` and `agents/` as they stand today import `memory`, `controlcenter.TiersConfig`, `skills`, `tooling` — which `ai/` must not depend on. The decomposition below reflects the revised `technical/ARCHITECTURE-v0.7.10.md` §2.3 and §2.5.
## Entry: coverage floor (per #343)
Before starting this step, every moving package must be at or above its TEST-BASELINE.md baseline:
| Package | Floor |
|---|---:|
| `internal/provider` | ≥ 52.7 % |
| `internal/router` | ≥ 62.5 % |
| `internal/agents` | ≥ 73.9 % |
| `internal/controlcenter` | ≥ 51.1 % (for `chat_service.go` migration to Runtime) |
| `internal/scheduler` | ≥ 56.2 % (for scheduler → Runtime migration) |
Additional blockers (entry criteria):
- **#345** — resolved. Data race on `TaskMeta` fixed before this step.
- **#371** — tracks intentional drift on `agents` / `controlcenter` / `router` during the migration.
## Deliverables — AI
- [x] **A1** — pin `ai.Engine` contract + `Event` shape invariants with compile-time test doubles (`internal/ai/engine_test.go`, `event_test.go`).
- [x] **A2** — absorb `internal/provider/` → `internal/ai/provider/` (sub-package for drivers: `APIProvider`, `CLIProvider`, `CodexProvider`, `Registry`, `History`, `ToolLoop`). `internal/provider/` reduced to thin re-export shim.
- [x] **A3** — extract `ResolveModel` + pure helpers from `router/` to `internal/ai/resolve.go`. `router.ResolveModel` reduced to string-cast shim calling `ai.ResolveModel`. Informational CI test (`TestHardcodedModelFallback`) scans non-test `.go` files outside `internal/ai/` for hardcoded `claude-{haiku|sonnet|opus}-*` literals. Currently reports 5 violations in `controlcenter/fallback.go` (cleaned in A5).
- [ ] **A4** — define the `ai.Strategy` contract (pure interface: "how to chain Engine.Run calls"). No `agents/` files move into `ai/` — the orchestrator imports memory/skills/tooling and must stay out of the AI block. The concrete orchestrator migrates to Runtime in R2.
- [ ] **A5** — delete shims (`internal/provider/shim.go`, `router.ResolveModel`) once consumers go through Runtime. Flip `TestHardcodedModelFallback` to enforcing.
## Deliverables — Runtime
- [ ] **R1** — skeleton `internal/runtime/` with `Chat(ctx, convID, userInput) <-chan Event` and `Invoke(ctx, capID, args) (Output, error)` signatures. No logic yet.
- [ ] **R2** — absorb the classifier half of `router/` (`BuildSystemPrompt`, `ParseResponse`, `InterpretRaw`, `ClassifyInput`, `FallbackResult`, `ValidTierSet`, `TierAccess`, `HasWriteIntent`) into `internal/runtime/classifier/`. Absorb `agents/` (orchestrator, session, store, prepare, prompt) into `internal/runtime/agents/`.
- [ ] **R3** — implement the pipeline: resolve Capability → load history from Memory → derive Policy → `Sandbox.Apply` → `AI.Run` → loop on ToolCall events → execute each Capability via Sandbox → persist turn.
- [ ] **R4** — migrate `controlcenter/chat_service.go` to consume `Runtime.Chat`.
- [ ] **R5** — migrate `scheduler/` to consume `Runtime.Invoke`.
- [ ] **R6** — migrate remaining consumers (telegram, cli). Physically delete shims (`internal/provider`, `internal/router`, `internal/agents`, `internal/firewall`, `internal/vault`, `internal/tooling/{sandbox,integrity}.go`, `internal/marketplace/permissions.go`). Flip `TestConsumerDependencyRules` and `TestHardcodedModelFallback` to enforcing. Closes #338 + #339 + #340 together.
## Hard rules (see arch doc §2.3, §2.5)
- Single `ResolveModel`. CI test fails on any hardcoded `claude-*` fallback outside `internal/ai/`.
- AI does not read Memory directly — Runtime prepares the Request.
- AI does not execute Capabilities — it emits `ToolCall` events; Runtime executes them via Sandbox.
- `ai/` provides the **contract** for `Strategy`. Concrete orchestrators (agents, classifier router) live in `runtime/`, not `ai/`.
- Runtime is the **only** package importing `capability + memory + ai + sandbox` together.
- No consumer (controlcenter, telegram, scheduler, cli) imports capability/memory/ai/sandbox directly.
## Acceptance
- `grep -r "hardcoded claude model"` CI test enforcing green.
- Chat flow end-to-end through Runtime.
- Scheduler job invocation goes through `Runtime.Invoke`.
- `internal/provider`, `internal/router`, `internal/agents` deleted.
- `make regression` + `make regression-race` green at HEAD of the ticket branch.
**Estimate**: 2–3 weeks (original estimate holds; the revised split is clearer, not bigger).