88a1dab810
---ci--- phase: 7 milestone: v0.2 status: complete requirements: covered: [REQ-2-001, REQ-2-002, REQ-2-003, REQ-2-004, REQ-2-005, REQ-2-006, REQ-2-007, REQ-2-008, REQ-2-009, REQ-2-010, REQ-2-011, REQ-2-012] partial: [] ---/ci--- Milestone v0.2 (ai-tutor-architecture) merged to main. Escalation record (audit remediation, durable): P1 executor delegation failed twice (empty subagent results, zero files created); auto-resolved at full autonomy to inline execution with identical plan fidelity (commit 3271373, reflog-only after phase branch squash-delete).
422 lines
35 KiB
Markdown
422 lines
35 KiB
Markdown
# Nextcraft v0.2 — PLAN.md
|
|
|
|
## Overview
|
|
|
|
This plan covers execution phases 1-6 of milestone v0.2 (AI Tutor Architecture): the six AI tutor agents (Coach, Tutor, Lab, Assessor, Proctor, Mentor) as real LLM-backed services in a new `apps/ai-service` Python FastAPI application, wired into the existing v0.1 learner surface with streaming responses. Phases are strictly sequential (P1→P6); within each phase, Wave 1 tasks are parallelizable (no cross-file dependencies) and later waves depend on earlier ones.
|
|
|
|
**Environment facts (apply throughout):** Python via `python3 -m venv` (no system pip, no uv); pnpm 12.3.4 via corepack; ai-service port **8420**; default model `gemma4:31b` (config via `AI_TUTOR_MODEL`); ollama-cloud base `https://ollama.com/v1` (OpenAI-compatible, Bearer auth); API keys live only in gitignored `.ciagent/.env.secrets`, exported by `scripts/dev.sh` — never in code, commits, or logs; all automated tests use the deterministic mock provider and **never call the cloud**.
|
|
|
|
| Phase | Name | Requirements | Waves | Personas |
|
|
|-------|------|-------------|-------|----------|
|
|
| 1 | AI service scaffolding | REQ-2-001, 002, 003 | 3 | backend-engineer, ai-engineer |
|
|
| 2 | Agent framework | REQ-2-004 | 2 | ai-engineer, backend-engineer |
|
|
| 3 | Coach + Tutor agents | REQ-2-005, 006 | 3 | ai-engineer, backend-engineer |
|
|
| 4 | Lab + Assessor agents | REQ-2-007, 008 | 4 | ai-engineer, backend-engineer, data-engineer |
|
|
| 5 | Proctor + Mentor agents | REQ-2-009, 010 | 3 | ai-engineer, backend-engineer |
|
|
| 6 | Learner surface integration | REQ-2-011, 012 | 3 | frontend-engineer, design-system-engineer |
|
|
|
|
---
|
|
|
|
## Phase 1: AI Service Scaffolding
|
|
|
|
**Requirements:** REQ-2-001, REQ-2-002, REQ-2-003
|
|
**Goal:** apps/ai-service runs under uvicorn, /health responds, provider-agnostic LLM layer with ollama-cloud/local/mock providers, SSE chat streaming verified, pytest suite green with mock provider, turbo integration wired
|
|
|
|
### Wave 1: Service shell + LLM core (parallel — no shared files)
|
|
|
|
#### Task 1-1-01: FastAPI app scaffolding
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-001
|
|
- **Files:** `apps/ai-service/pyproject.toml`, `apps/ai-service/ai_service/main.py`, `apps/ai-service/ai_service/config.py`, `apps/ai-service/ai_service/__init__.py`, `apps/ai-service/tests/conftest.py`, `apps/ai-service/tests/test_health.py`, `apps/ai-service/.env.example`, `apps/ai-service/README.md`
|
|
- **Action:** pydantic-settings `Settings` (env_prefix `AI_`, env_file, `SecretStr` key, port 8420, provider select, `AI_TUTOR_MODEL` default `gemma4:31b`). FastAPI app factory in `main.py` with lifespan stub (httpx client pool comes in Wave 2), CORS localhost-only (A-008), `GET /health`. pyproject with pinned deps (fastapi, uvicorn, pydantic, pydantic-settings, httpx, sse-starlette, pytest, pytest-asyncio) and dev extra. conftest: settings override + TestClient fixture. `.env.example` documents all `AI_*` vars; README documents venv setup and dev workflow.
|
|
- **Verify:** `scripts/bootstrap.sh && scripts/test.sh` — test_health passes; `curl localhost:8420/health` returns 200
|
|
|
|
#### Task 1-1-02: LLM types, protocol, mock provider
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-002
|
|
- **Files:** `apps/ai-service/ai_service/llm/types.py`, `apps/ai-service/ai_service/llm/base.py`, `apps/ai-service/ai_service/llm/mock.py`, `apps/ai-service/ai_service/llm/__init__.py`
|
|
- **Action:** `types.py`: pydantic `Message` (role/content), `ChatDelta` (OpenAI-compatible chunk shape). `base.py`: `LLMProvider` protocol — async `stream_chat(messages, model, response_format=None) -> AsyncIterator[ChatDelta]`; the provider is a dumb pipe, no envelope logic (D-016 keeps envelope in API layer). `mock.py`: deterministic scripted provider (hash-seeded token streams, scripted failure modes: connect error, mid-stream error, malformed JSON) for tests and CI.
|
|
- **Verify:** mock provider importable and deterministic; two identical calls yield identical streams
|
|
|
|
#### Task 1-1-03: Monorepo integration (shim + turbo + scripts)
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-001
|
|
- **Files:** `apps/ai-service/package.json`, `apps/ai-service/scripts/bootstrap.sh`, `apps/ai-service/scripts/dev.sh`, `apps/ai-service/scripts/test.sh`, `turbo.json` (update), `package.json` (root, update)
|
|
- **Action:** zero-dependency shim `package.json` in apps/ai-service with `dev`/`test`/`bootstrap` script entries. Turbo passthrough tasks `ai#dev`, `ai#test`, `ai#bootstrap` (`cache: false`, `outputs: []`). Root scripts `ai:dev`, `ai:test`, `ai:bootstrap`. `bootstrap.sh`: idempotent `python3 -m venv .venv` + pip install. `dev.sh`: exports keys from `.ciagent/.env.secrets` → uvicorn on 8420. `test.sh`: pytest via venv.
|
|
- **Verify:** `corepack pnpm install && pnpm ai:bootstrap && pnpm ai:test` runs pytest through turbo; re-running bootstrap is a no-op
|
|
|
|
#### Task 1-1-04: Python lint (ruff, check-only)
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-001
|
|
- **Files:** `apps/ai-service/pyproject.toml` (update: `[tool.ruff]` config + `ruff` in dev extra), `apps/ai-service/scripts/lint.sh`, `package.json` (root, update), `turbo.json` (update)
|
|
- **Action:** Add `ruff` (check-only, no formatter) to the dev extra; `[tool.ruff]` with line-length 100, target py311. `scripts/lint.sh`: `.venv/bin/ruff check .` with repo-root path resolution via script-relative dirname (not CWD). Root script `ai:lint`, turbo passthrough `ai#lint` (`cache: false`, `outputs: []`). Run over the entire ai-service tree; fix all findings before phase ship (G-3).
|
|
- **Verify:** `pnpm ai:lint` exits 0 on the Phase 1 codebase
|
|
|
|
### Wave 2: Real providers + SSE endpoint (depends on Wave 1)
|
|
|
|
#### Task 1-2-01: OpenAI-compatible provider + factory
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-002
|
|
- **Files:** `apps/ai-service/ai_service/llm/openai_compat.py`, `apps/ai-service/ai_service/llm/factory.py`
|
|
- **Action:** `openai_compat.py`: single `OpenAICompatProvider` for ollama-cloud (`https://ollama.com/v1`, Bearer) and local endpoints (base URL from settings); raw httpx against `/v1/chat/completions` with `stream: true`, byte-identical delta passthrough, tolerant of ollama-cloud quirks. Uses the lifespan-managed `httpx.AsyncClient` (10s connect / 300s read, D-017) — no openai SDK. `factory.py`: select provider from settings (`ollama-cloud` | `local` | `mock`).
|
|
- **Verify:** provider constructs from settings for all 3 names; manual probe against ollama-cloud streams tokens (documented in README, not a test)
|
|
|
|
#### Task 1-2-02: Lifespan wiring + SSE chat endpoint
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-003
|
|
- **Files:** `apps/ai-service/ai_service/main.py` (update), `apps/ai-service/ai_service/api/deps.py`, `apps/ai-service/ai_service/api/chat.py`, `apps/ai-service/ai_service/api/__init__.py`
|
|
- **Action:** Lifespan creates the shared `httpx.AsyncClient` and provider factory; deps.py provides provider via DI. `POST /v1/chat/stream` in chat.py implements the D-016 envelope: `meta` event (agent/session/model) flushed before first token → raw OpenAI chunks passed through as `data: {json}` → `done` event → `error` event before `[DONE]` on mid-stream failure; pre-first-byte failures return proper HTTP status codes. Headers `Cache-Control: no-cache`, `X-Accel-Buffering: no`; sse-starlette ping keep-alive.
|
|
- **Verify:** `curl -N -X POST localhost:8420/v1/chat/stream` with mock provider shows meta event, token deltas, done, `[DONE]`
|
|
|
|
### Wave 3: Provider + endpoint test suites (depends on Wave 2)
|
|
|
|
#### Task 1-3-01: LLM provider tests (byte-exact, no cloud)
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-002
|
|
- **Files:** `apps/ai-service/tests/llm/test_openai_compat.py`, `apps/ai-service/tests/llm/test_mock.py`, `apps/ai-service/tests/llm/__init__.py`
|
|
- **Action:** httpx `MockTransport` tests parsing byte-exact fixture streams (happy path, empty delta, `[DONE]`, malformed line, mid-stream disconnect). Mock provider tests: determinism, scripted failure modes, response_format echo.
|
|
- **Verify:** `pnpm ai:test` — llm suite green; zero network calls in tests
|
|
|
|
#### Task 1-3-02: SSE stream endpoint tests
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-003
|
|
- **Files:** `apps/ai-service/tests/api/test_chat_stream.py`, `apps/ai-service/tests/api/__init__.py`
|
|
- **Action:** TestClient `client.stream()` tests: meta-first ordering, delta passthrough, done + `[DONE]` sentinel, mid-stream error event, pre-first-byte failure → HTTP status, required headers. pytest-asyncio auto mode (D-023).
|
|
- **Verify:** `pnpm ai:test` — api suite green
|
|
|
|
### Must-Haves (Phase 1)
|
|
- [ ] `scripts/bootstrap.sh` is idempotent; creates venv + installs deps without system pip
|
|
- [ ] `pnpm ai:dev` starts uvicorn; `curl localhost:8420/health` returns 200
|
|
- [ ] `pnpm ai:lint` exits 0 (ruff check over the ai-service tree) (G-3)
|
|
- [ ] `pnpm ai:test` runs the full pytest suite via turbo and passes (mock provider only — no network)
|
|
- [ ] SSE stream delivers tokens: meta event, incremental deltas, done, `[DONE]` observed via `curl -N`
|
|
- [ ] Mid-stream failure emits `error` event before `[DONE]`; pre-first-byte failure returns HTTP error status
|
|
- [ ] Provider factory resolves ollama-cloud / local / mock from settings; manual ollama-cloud probe documented in README
|
|
- [ ] `llm/` imports nothing from `agents/` or `api/` (boundary rule holds)
|
|
|
|
---
|
|
|
|
## Phase 2: Agent Framework
|
|
|
|
**Requirements:** REQ-2-004
|
|
**Goal:** Shared framework all six agents use: BaseAgent contract, session store, prompt library, registry, structured outputs — all tested against the mock provider
|
|
|
|
### Wave 1: Framework primitives (parallel — no shared files)
|
|
|
|
#### Task 2-1-01: BaseAgent ABC
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-004
|
|
- **Files:** `apps/ai-service/ai_service/agents/base.py`, `apps/ai-service/tests/agents/test_base.py`, `apps/ai-service/ai_service/agents/__init__.py`, `apps/ai-service/tests/agents/__init__.py`
|
|
- **Action:** `BaseAgent` ABC (D-018): `name`, `system_prompt`, `build_messages(history, learner_context)`, `stream_reply(...) -> AsyncIterator[ChatDelta]` (delegates to provider), `structured_reply(...)` (delegates to structured module, landed Wave 2). Subclass contract tested with a stub agent + mock provider.
|
|
- **Verify:** `pnpm ai:test` — test_base green
|
|
|
|
#### Task 2-1-02: SessionStore protocol + in-memory implementation
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-004
|
|
- **Files:** `apps/ai-service/ai_service/agents/session.py`, `apps/ai-service/tests/test_session.py`
|
|
- **Action:** `SessionStore` protocol + `InMemorySessionStore` (D-019): asyncio.Lock-guarded dict, agent-scoped session keys, 20-message rolling window, 500-cap LRU eviction. Protocol shape is DB-migration-ready (A-003).
|
|
- **Verify:** test_session covers create/append/window-trim/LRU-eviction/agent scoping
|
|
|
|
#### Task 2-1-03: Prompt library scaffolding
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-004
|
|
- **Files:** `apps/ai-service/ai_service/prompts/coach.py`, `apps/ai-service/ai_service/prompts/tutor.py`, `apps/ai-service/ai_service/prompts/lab.py`, `apps/ai-service/ai_service/prompts/assessor.py`, `apps/ai-service/ai_service/prompts/proctor.py`, `apps/ai-service/ai_service/prompts/mentor.py`, `apps/ai-service/ai_service/prompts/__init__.py`
|
|
- **Action:** Per-agent module with a versioned `SYSTEM_PROMPT` constant + `render_context(learner_context) -> dict` using `str.format_map` for learner-context injection (D-018: prompts are code, versioned in git). Initial drafts for all six; final personas land in Phases 3-5.
|
|
- **Verify:** all six prompt modules import; render_context fills placeholders without KeyError
|
|
|
|
#### Task 2-1-04: Learner context corpus
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-004
|
|
- **Files:** `apps/ai-service/ai_service/corpus/learner_context.py`, `apps/ai-service/ai_service/corpus/__init__.py`
|
|
- **Action:** Pydantic-typed learner context (active stack, competencies, progress, recent artifacts) mirroring TS `packages/mock-data` IDs per D-021 convention (cross-referencing header comment, identical `comp-*`/`stack-*` ID strings).
|
|
- **Verify:** context renders into prompt placeholders; IDs match packages/mock-data strings
|
|
|
|
### Wave 2: Composition layers (depends on Wave 1)
|
|
|
|
#### Task 2-2-01: Structured output defense
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-004
|
|
- **Files:** `apps/ai-service/ai_service/agents/structured.py`, `apps/ai-service/tests/test_structured.py`
|
|
- **Action:** 4-layer defense (D-020): (1) `response_format` request with auto-degrade on provider 400; (2) prompt-embedded JSON schema; (3) parse: strip code fences → first balanced JSON object; (4) single bounded retry with validation-error feedback. Returns pydantic-validated model or raises `StructuredOutputError`.
|
|
- **Verify:** test_structured covers fenced/unfenced/invalid JSON, retry path, degrade path — all against mock provider
|
|
|
|
#### Task 2-2-02: Agent registry
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-004
|
|
- **Files:** `apps/ai-service/ai_service/agents/registry.py`, `apps/ai-service/tests/test_registry.py`
|
|
- **Action:** Explicit registry: name → agent factory map with `register(name, factory)` / `get(name)`; raises on unknown agent. Agents are registered in their own phases (P3-P5).
|
|
- **Verify:** test_registry: register/get round-trip, unknown-agent error, duplicate registration error
|
|
|
|
#### Task 2-2-03: Session + agent DI wiring into API layer
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-004
|
|
- **Files:** `apps/ai-service/ai_service/api/deps.py` (update), `apps/ai-service/ai_service/api/chat.py` (update)
|
|
- **Action:** deps.py exposes SessionStore and provider singletons via DI. chat.py persists turn history through the session store (agent-scoped) and includes session ID in the meta event. API composes agents via DI — agents never import api/.
|
|
- **Verify:** chat request appends to and replays windowed history; `pnpm ai:test` green
|
|
|
|
### Must-Haves (Phase 2)
|
|
- [ ] BaseAgent unit tests pass (stub agent streams via mock provider)
|
|
- [ ] Session store tested: create/append, 20-message window trim, 500-cap LRU eviction, agent-scoped keys
|
|
- [ ] Structured output parsing tested against mock provider: fence-strip, first-balanced-object, invalid JSON, one bounded retry, response_format auto-degrade
|
|
- [ ] Registry tested: register/get/unknown/duplicate
|
|
- [ ] Six prompt modules render learner context without errors
|
|
- [ ] Module boundaries hold: `agents/` never imports `api/`; `llm/` never imports `agents/` or `api/`
|
|
|
|
---
|
|
|
|
## Phase 3: Coach + Tutor Agents
|
|
|
|
**Requirements:** REQ-2-005, REQ-2-006
|
|
**Goal:** Both learner-facing conversational agents fully implemented with distinct personas, registered, routed through the chat streaming endpoint
|
|
|
|
### Wave 1: Agent implementations (parallel — no shared files)
|
|
|
|
#### Task 3-1-01: Coach agent
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-005
|
|
- **Files:** `apps/ai-service/ai_service/prompts/coach.py` (finalize), `apps/ai-service/ai_service/agents/coach.py`, `apps/ai-service/tests/test_coach.py`
|
|
- **Action:** Final Coach persona: pacing guidance, motivation, retrieval practice prompts; system prompt injects learner context (active stack, progress). `CoachAgent(BaseAgent)` streams replies. Mock provider scripts a distinct coach-voice response for tests.
|
|
- **Verify:** test_coach: build_messages includes system prompt + windowed history; stream_reply yields deltas; on-persona content asserted against mock script
|
|
|
|
#### Task 3-1-02: Tutor agent
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-006
|
|
- **Files:** `apps/ai-service/ai_service/prompts/tutor.py` (finalize), `apps/ai-service/ai_service/agents/tutor.py`, `apps/ai-service/tests/test_tutor.py`
|
|
- **Action:** Final Tutor persona: concept delivery, Socratic questioning, worked examples. `TutorAgent(BaseAgent)` streams replies; mock scripts a distinct tutor-voice response.
|
|
- **Verify:** test_tutor mirrors test_coach; Coach and Tutor mock outputs are observably distinct
|
|
|
|
### Wave 2: Registration + persona verification (depends on Wave 1)
|
|
|
|
#### Task 3-2-01: Register Coach + Tutor; document cloud probe
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-005, REQ-2-006
|
|
- **Files:** `apps/ai-service/ai_service/agents/registry.py` (update), `apps/ai-service/tests/test_registry.py` (update), `apps/ai-service/README.md` (update)
|
|
- **Action:** Register both agents in the explicit registry. Extend test_registry to assert both resolve. Document the manual ollama-cloud persona probe in README (curl commands with `AI_PROVIDER=ollama-cloud`): Coach and Tutor produce distinct on-persona responses; tests remain cloud-free.
|
|
- **Verify:** `pnpm ai:test` green; manual probe against ollama-cloud shows distinct personas (documented, not automated)
|
|
|
|
### Wave 3: Chat endpoint agent routing (depends on Wave 2)
|
|
|
|
#### Task 3-3-01: Agent routing on /v1/chat/stream
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-005, REQ-2-006
|
|
- **Files:** `apps/ai-service/ai_service/api/chat.py` (update), `apps/ai-service/ai_service/api/deps.py` (update), `apps/ai-service/tests/api/test_chat_stream.py` (update)
|
|
- **Action:** Chat request gains `agent` field (validated against the registry; unknown agent → 422). Endpoint resolves the agent via DI, persists to the agent-scoped session, meta event carries the agent name. No autonomous routing in v0.2 (A-007).
|
|
- **Verify:** TestClient tests: `agent=coach` and `agent=tutor` route correctly, session scoped per agent, unknown agent rejected
|
|
|
|
### Must-Haves (Phase 3)
|
|
- [ ] Both agents produce distinct, on-persona responses (mock-asserted; manual ollama-cloud probe documented in README)
|
|
- [ ] Agent routing tested: coach/tutor resolve via registry; unknown agent returns 422
|
|
- [ ] Both agents exposed end-to-end via `POST /v1/chat/stream` with agent-scoped session history
|
|
- [ ] `pnpm ai:test` green; no cloud calls in tests
|
|
|
|
---
|
|
|
|
## Phase 4: Lab + Assessor Agents
|
|
|
|
**Requirements:** REQ-2-007, REQ-2-008
|
|
**Goal:** Lab consumes simulated sandbox telemetry and streams in-flow feedback; Assessor applies rubrics to pre-baked artifacts and returns structured scores — both over mock engine inputs
|
|
|
|
### Wave 1: Mock engine inputs (parallel — no shared files)
|
|
|
|
#### Task 4-1-01: Simulated sandbox telemetry corpus
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-007
|
|
- **Files:** `apps/ai-service/ai_service/corpus/telemetry.py`
|
|
- **Action:** Pydantic-typed Lab telemetry scenarios: scripted build-session event streams (keystrokes, commits, test runs, errors, idle gaps) keyed by scenario ID, aligned with packages/mock-data IDs (D-021).
|
|
- **Verify:** scenarios import, validate, and are addressable by ID
|
|
|
|
#### Task 4-1-02: Pre-baked artifacts + rubrics corpus
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-008
|
|
- **Files:** `apps/ai-service/ai_service/corpus/artifacts.py`
|
|
- **Action:** Pre-baked artifacts (code, design, simulation), assessment rubrics (criteria, levels, weights), and defense transcripts keyed by ID — the Assessor's mock inputs (real engines are v0.3+).
|
|
- **Verify:** rubric/artifact/transcript fixtures validate; IDs align with TS mock data
|
|
|
|
#### Task 4-1-03: TS mock-data alignment for engine inputs
|
|
- **Persona:** data-engineer — **REQ:** REQ-2-012
|
|
- **Files:** `packages/mock-data/ai-scenarios.ts` (new), `packages/mock-data/index.ts` (update)
|
|
- **Action:** Export scenario/artifact ID constants + display metadata used by the Phase 6 learner panels, mirroring `ai_service/corpus/` IDs exactly (D-021). Data-engineer owns the TS side; headers cross-reference the Python corpus.
|
|
- **Verify:** `pnpm typecheck` passes; IDs string-equal to corpus IDs
|
|
|
|
### Wave 2: Agent implementations (depends on Wave 1)
|
|
|
|
#### Task 4-2-01: Lab agent
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-007
|
|
- **Files:** `apps/ai-service/ai_service/prompts/lab.py` (finalize), `apps/ai-service/ai_service/agents/lab.py`, `apps/ai-service/tests/test_lab.py`
|
|
- **Action:** `LabAgent(BaseAgent)` consumes a telemetry scenario, builds messages summarizing the event stream, streams concrete in-flow feedback (what happened, what to adjust, next step). No session chat — scenario-driven.
|
|
- **Verify:** test_lab: given a mock scenario, feedback references scenario events (mock-scripted assertions)
|
|
|
|
#### Task 4-2-02: Assessor agent
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-008
|
|
- **Files:** `apps/ai-service/ai_service/prompts/assessor.py` (finalize), `apps/ai-service/ai_service/agents/assessor.py`, `apps/ai-service/tests/test_assessor.py`
|
|
- **Action:** `AssessorAgent(BaseAgent)` applies a rubric to an artifact + defense transcript via `structured_reply`, returning a pydantic-validated rubric score model (per-criterion scores, strengths, gaps, verdict).
|
|
- **Verify:** test_assessor: structured output validates against the rubric model; failure modes exercise the 4-layer defense
|
|
|
|
### Wave 3: Registration (depends on Wave 2)
|
|
|
|
#### Task 4-3-01: Register Lab + Assessor
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-007, REQ-2-008
|
|
- **Files:** `apps/ai-service/ai_service/agents/registry.py` (update), `apps/ai-service/tests/test_registry.py` (update)
|
|
- **Action:** Register both agents; extend registry tests.
|
|
- **Verify:** registry resolves coach/tutor/lab/assessor; `pnpm ai:test` green
|
|
|
|
### Wave 4: Endpoints (depends on Wave 3)
|
|
|
|
#### Task 4-4-01: Lab feedback endpoint
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-007
|
|
- **Files:** `apps/ai-service/ai_service/api/lab.py`, `apps/ai-service/tests/api/test_lab.py`
|
|
- **Action:** `POST /v1/lab/feedback` with scenario ID → resolves corpus scenario + Lab agent → SSE stream using the D-016 envelope (meta names agent=lab). Unknown scenario → 404.
|
|
- **Verify:** TestClient streams meta + deltas + done + `[DONE]`; unknown scenario 404
|
|
|
|
#### Task 4-4-02: Assessment evaluate endpoint
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-008
|
|
- **Files:** `apps/ai-service/ai_service/api/assessment.py`, `apps/ai-service/tests/api/test_assessment.py`
|
|
- **Action:** `POST /v1/assessment/evaluate` with artifact ID → resolves corpus artifact/rubric/transcript + Assessor agent → JSON response (validated rubric score model). Unknown artifact → 404.
|
|
- **Verify:** TestClient returns validated rubric JSON; unknown artifact 404; `pnpm ai:test` green
|
|
|
|
### Must-Haves (Phase 4)
|
|
- [ ] Lab produces scenario-relevant in-flow feedback for mock telemetry scenarios (mock provider, tested)
|
|
- [ ] Assessor returns structured rubric scores (pydantic-validated JSON) for pre-baked artifacts/transcripts
|
|
- [ ] `POST /v1/lab/feedback` streams (meta → deltas → done → `[DONE]`); `POST /v1/assessment/evaluate` returns validated JSON
|
|
- [ ] Unknown scenario/artifact IDs return 404
|
|
- [ ] Corpus IDs align with packages/mock-data (D-021); `pnpm ai:test` and `pnpm typecheck` green
|
|
|
|
---
|
|
|
|
## Phase 5: Proctor + Mentor Agents
|
|
|
|
**Requirements:** REQ-2-009, REQ-2-010
|
|
**Goal:** Proctor classifies integrity signals with coaching interventions from mock telemetry; Mentor generates long-horizon career narrative; both exposed via endpoints
|
|
|
|
### Wave 1: Proctor scenarios + Mentor agent (parallel — no shared files)
|
|
|
|
#### Task 5-1-01: Proctor telemetry scenarios
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-009
|
|
- **Files:** `apps/ai-service/ai_service/corpus/telemetry.py` (update)
|
|
- **Action:** Add proctor scenarios: tab switches, idle time, paste events, focus loss — scripted integrity-relevant event sets keyed by scenario ID.
|
|
- **Verify:** proctor scenarios validate; distinguishable from lab scenarios by type
|
|
|
|
#### Task 5-1-02: Mentor agent (+ registration)
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-010
|
|
- **Files:** `apps/ai-service/ai_service/prompts/mentor.py` (finalize), `apps/ai-service/ai_service/agents/mentor.py`, `apps/ai-service/tests/test_mentor.py`, `apps/ai-service/ai_service/agents/registry.py` (update)
|
|
- **Action:** `MentorAgent(BaseAgent)`: long-horizon career narrative — trajectory story, competency-stack progression guidance, market positioning — streaming, session-backed. Registered centrally in `registry.py` (single registration pattern, G-4).
|
|
- **Verify:** test_mentor: narrative references learner context (mock-scripted); registry resolves mentor
|
|
|
|
### Wave 2: Proctor agent + Mentor endpoint (depends on Wave 1)
|
|
|
|
#### Task 5-2-01: Proctor agent (+ registration)
|
|
- **Persona:** ai-engineer — **REQ:** REQ-2-009
|
|
- **Files:** `apps/ai-service/ai_service/prompts/proctor.py` (finalize), `apps/ai-service/ai_service/agents/proctor.py`, `apps/ai-service/tests/test_proctor.py`, `apps/ai-service/ai_service/agents/registry.py` (update)
|
|
- **Action:** `ProctorAgent(BaseAgent)`: consumes proctor scenario → `structured_reply` returns pydantic-validated signal classification (severity, signal type) + recommended coaching intervention (supportive, not punitive). Registered centrally in `registry.py` (single registration pattern, G-4).
|
|
- **Verify:** test_proctor: classified signals + interventions validate for each mock scenario; registry resolves all six agents
|
|
|
|
#### Task 5-2-02: Mentor narrative endpoint
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-010
|
|
- **Files:** `apps/ai-service/ai_service/api/mentor.py`, `apps/ai-service/tests/api/test_mentor.py`
|
|
- **Action:** `POST /v1/mentor/narrative` → Mentor agent → SSE stream with D-016 envelope, session-backed.
|
|
- **Verify:** TestClient streams meta (agent=mentor) → deltas → done → `[DONE]`
|
|
|
|
### Wave 3: Proctor endpoint (depends on Wave 2)
|
|
|
|
#### Task 5-3-01: Proctor signals endpoint
|
|
- **Persona:** backend-engineer — **REQ:** REQ-2-009
|
|
- **Files:** `apps/ai-service/ai_service/api/proctor.py`, `apps/ai-service/tests/api/test_proctor.py`
|
|
- **Action:** `POST /v1/proctor/signals` with scenario ID → resolves corpus scenario + Proctor agent → JSON response (validated signals + interventions). Unknown scenario → 404.
|
|
- **Verify:** TestClient returns classified signals JSON; `pnpm ai:test` green — full suite (all six agents registered)
|
|
|
|
### Must-Haves (Phase 5)
|
|
- [ ] Proctor produces classified signals with recommended coaching interventions for each mock scenario (structured JSON, validated)
|
|
- [ ] Mentor produces coherent long-horizon career narrative (streaming, session-backed)
|
|
- [ ] `POST /v1/proctor/signals` returns validated JSON; `POST /v1/mentor/narrative` streams
|
|
- [ ] Registry resolves all six agents; full ai-service test suite green, cloud-free
|
|
|
|
---
|
|
|
|
## Phase 6: Learner Surface Integration
|
|
|
|
**Requirements:** REQ-2-011, REQ-2-012
|
|
**Goal:** v0.1 learner surfaces wired to the real ai-service: streaming chat with agent switcher, Lab/Assessor/Proctor/Mentor outputs surfaced, error/loading states, build + typecheck green
|
|
**Note (G-2):** End-to-end verification may run with `AI_PROVIDER=mock` as a fallback — the requirement is the real ai-service over HTTP (not canned client-side responses); provider choice is service-internal. This prevents an ollama-cloud outage from blocking P6 verification. Cloud persona probes remain separate (Task 3-2-01).
|
|
|
|
### Wave 1: Client plumbing + primitives (parallel — no shared files)
|
|
|
|
#### Task 6-1-01: useChatStream hook
|
|
- **Persona:** frontend-engineer — **REQ:** REQ-2-011
|
|
- **Files:** `apps/web/hooks/use-chat-stream.ts`, `apps/web/.env.example` (update)
|
|
- **Action:** `useChatStream(agent)` hook: `fetch` POST to `${NEXT_PUBLIC_AI_SERVICE_URL}/v1/chat/stream` (default `http://localhost:8420`, A-002 — no API-route proxy); consumes `ReadableStream` with byte buffering, frame split on `\n\n`, joined `data:` lines; **ignores frames containing no `data:` lines (sse-starlette `: ping` keep-alive comment frames)** — TestClient streams are too short to surface pings, but real cloud delta gaps emit them (G-1); handles meta / delta / done / error events and `[DONE]` sentinel; idempotent `AbortController.abort()` in effect cleanup; exposes `{messages, isStreaming, error, send, retry, abort}`. `.env.example` gains `NEXT_PUBLIC_AI_SERVICE_URL`.
|
|
- **Verify:** hook unit-tested or exercised via the chat UI; unmount mid-stream aborts cleanly (no state updates after unmount)
|
|
|
|
#### Task 6-1-02: Agent switcher + streaming primitives
|
|
- **Persona:** design-system-engineer — **REQ:** REQ-2-011
|
|
- **Files:** `packages/ui/src/primitives/agent-switcher.tsx`, `packages/ui/src/primitives/stream-status.tsx`, `packages/ui/src/primitives/toast.tsx`, `packages/ui/src/primitives/index.ts` (update), `packages/ui/src/index.ts` (update)
|
|
- **Action:** Token-driven primitives: AgentSwitcher (segmented coach/tutor control with active state), StreamStatus (idle/streaming/error indicator), Toast with error variant. Dark mode + WCAG AA contrast; exported from `@nextcraft/ui`.
|
|
- **Verify:** primitives import from `@nextcraft/ui`; storybook stories render (dark + light)
|
|
|
|
### Wave 2: Chat rewrite + Byte viewer panel (depends on Wave 1)
|
|
|
|
#### Task 6-2-01: Real streaming learner chat
|
|
- **Persona:** frontend-engineer — **REQ:** REQ-2-011
|
|
- **Files:** `apps/web/components/learner/ai-tutor-chat.tsx` (rewrite), `apps/web/components/learner/agent-switcher.tsx` (composition wrapper)
|
|
- **Action:** Replace the canned `aiTutorResponses` behavior with `useChatStream`: agent switcher (Coach/Tutor per A-007), token-by-token rendering, streaming cursor + loading state, error state with retry button when ai-service is down (A-010), suggested-action chips from the meta event. Seed welcome message stays static.
|
|
- **Verify:** with ai-service running, messages stream visibly token-by-token; with ai-service stopped, error state + retry appears (no crash, no console errors)
|
|
|
|
#### Task 6-2-02: Byte viewer Tutor panel
|
|
- **Persona:** frontend-engineer — **REQ:** REQ-2-011 (agent routing: byte viewer always uses Tutor, A-007)
|
|
- **Files:** `apps/web/app/(learner)/learn/[competencyId]/page.tsx` (update), `apps/web/components/learner/byte-tutor-panel.tsx` (new)
|
|
- **Action:** Byte viewer gains a Tutor explanation panel (byte viewer always uses Tutor, A-007): "Explain this byte" streams a Socratic concept walkthrough for the current competency via useChatStream (agent fixed to tutor).
|
|
- **Verify:** on a byte page, the panel streams a Tutor explanation; error state when service down
|
|
|
|
### Wave 3: Lab / Assessment / Mentor panels (depends on Wave 1 hook; parallel — no shared files)
|
|
|
|
#### Task 6-3-01: Sandbox Lab feedback panel
|
|
- **Persona:** frontend-engineer — **REQ:** REQ-2-012
|
|
- **Files:** `apps/web/app/(learner)/build/[competencyId]/page.tsx` (update), `apps/web/components/learner/lab-feedback-panel.tsx` (new)
|
|
- **Action:** Sandbox telemetry sidebar gains a Lab feedback panel: posts the scenario ID (from `packages/mock-data/ai-scenarios`) to `/v1/lab/feedback`, streams in-flow feedback into the panel; loading + error states.
|
|
- **Verify:** sandbox page streams Lab feedback for the mock scenario; error state when service down
|
|
|
|
#### Task 6-3-02: Assessment Assessor + Proctor surfaces
|
|
- **Persona:** frontend-engineer — **REQ:** REQ-2-012
|
|
- **Files:** `apps/web/app/(learner)/defend/[competencyId]/page.tsx` (update), `apps/web/components/learner/assessor-results-panel.tsx` (new), `apps/web/components/learner/proctor-banner.tsx` (new)
|
|
- **Action:** Assessment mockup: AI reviewer panel calls `/v1/assessment/evaluate` with the artifact ID and renders the structured rubric scores (per-criterion bars, strengths, gaps, verdict); a Proctor integrity banner surfaces `/v1/proctor/signals` classifications with coaching tone. Loading skeletons + error states.
|
|
- **Verify:** defend page renders real Assessor rubric output + Proctor banner; error states when service down
|
|
|
|
#### Task 6-3-03: Dashboard Mentor panel
|
|
- **Persona:** frontend-engineer — **REQ:** REQ-2-012
|
|
- **Files:** `apps/web/app/(learner)/dashboard/page.tsx` (update), `apps/web/components/learner/mentor-panel.tsx` (new)
|
|
- **Action:** Learner dashboard gains a Mentor panel: streams career narrative from `/v1/mentor/narrative` (learner progress context), with regenerate button, loading + error states. Sits alongside the existing AI tutor chat.
|
|
- **Verify:** dashboard shows streaming Mentor narrative; error state when service down
|
|
|
|
### Must-Haves (Phase 6)
|
|
- [ ] With ai-service running: learner chat at http://localhost:3000/dashboard streams real responses token-by-token (mock provider fallback allowed per G-2 — service over HTTP is the requirement)
|
|
- [ ] Agent switcher flips Coach ↔ Tutor and the response persona changes accordingly
|
|
- [ ] Hook tolerates keep-alive comment frames (`: ping`, no data lines) during live streams (G-1)
|
|
- [ ] With ai-service stopped: all chat/panels show error states with retry — no crashes, no unhandled promise rejections, no console errors
|
|
- [ ] Byte viewer, sandbox, and assessment mockups surface Tutor/Lab/Assessor/Proctor outputs; dashboard shows Mentor narrative
|
|
- [ ] Unmounting/navigating mid-stream aborts cleanly (no post-unmount state updates)
|
|
- [ ] `pnpm build` and `pnpm typecheck` pass; `pnpm ai:test` still green
|
|
|
|
---
|
|
|
|
## Phase 7: Final Review + Ship (no planned tasks)
|
|
|
|
Orchestrated by the SHIP stage, not this plan: multi-persona code review (correctness, testing, secrets hygiene — key absent from code/logs/commits/errors, localhost-only CORS, no PII in prompts), project health audit (reconstruction test, .ciagent/ discipline, branch/commit hygiene), then merge milestone → main, tag v0.2.0, create Gitea release, mark all 12 v0.2 requirements complete.
|
|
|
|
**Release-note honesty (G-5):** the v0.2.0 release note must explicitly state that Lab/Assessor/Proctor operate on mock engine inputs (real engines are v0.3+), per D-015.
|
|
|
|
**Dead-code disposal (G-5):** review must dispose of `aiTutorResponses` (packages/mock-data/ai-tutor-responses.ts) — its only consumer is rewritten in Task 6-2-01; remove the export or mark it deprecated.
|
|
|
|
---
|
|
|
|
## User-Facing Surface
|
|
|
|
The primary user-facing surface is the **learner dashboard and learning flow** at `http://localhost:3000`, backed by the real ai-service at `http://localhost:8420`:
|
|
|
|
- `/dashboard` — AI tutor chat (Coach/Tutor switcher, streaming) + Mentor career-narrative panel
|
|
- `/learn/[competencyId]` — byte viewer with streaming Tutor explanations
|
|
- `/build/[competencyId]` — sandbox with Lab in-flow feedback panel (mock telemetry)
|
|
- `/defend/[competencyId]` — assessment with live Assessor rubric scores + Proctor integrity banner
|
|
|
|
The marketplace, employer, and admin surfaces are unchanged from v0.1.
|
|
|
|
## Happy Path
|
|
|
|
1. Learner opens `/dashboard` → chat shows welcome message; meta event confirms coach/model in the stream
|
|
2. Learner types "I'm stuck on multi-agent communication" → reply streams token-by-token with pacing guidance + a retrieval-practice prompt
|
|
3. Learner switches to **Tutor** → asks the same question → gets a Socratic concept walkthrough instead
|
|
4. Learner opens a byte tutorial → Tutor panel streams an explanation of the current competency
|
|
5. Learner opens the build sandbox → Lab panel streams feedback on the simulated telemetry scenario
|
|
6. Learner opens the defense mockup → Assessor panel shows structured rubric scores; Proctor banner shows integrity signals in coaching tone
|
|
7. Back on `/dashboard`, the Mentor panel streams a career narrative tied to the learner's progress
|
|
8. Learner kills ai-service (or it crashes) → next message shows an inline error state with **Retry**; restarting the service and retrying resumes streaming
|
|
|
|
## UX Acceptance Criteria
|
|
|
|
1. Streaming is visibly incremental — tokens appear as they arrive, not as one blob
|
|
2. Agent switcher shows the active agent (Coach/Tutor) and the response persona visibly changes
|
|
3. Loading state during connection (streaming cursor / skeleton) before first token
|
|
4. When ai-service is unreachable: inline error state + retry action on every chat/panel — no crashes, no console errors, no blank UI
|
|
5. `[DONE]` reliably ends the stream (input re-enables, no stuck "typing" state)
|
|
6. Navigating away mid-stream aborts cleanly — no leaked requests or post-unmount updates
|
|
7. All new UI uses design tokens, supports dark mode, meets WCAG AA contrast
|
|
8. Responsive at 375px, 768px, 1280px
|
|
9. No hardcoded model names or URLs in UI code — all via `NEXT_PUBLIC_AI_SERVICE_URL` and server meta events
|
|
10. `pnpm build` and `pnpm typecheck` pass with zero errors |