mirror of
https://github.com/computerim/impactflow-discovery.git
synced 2026-08-27 06:20:36 +00:00
Phase 2: AI coach reflection loop
Add the mirror-not-compass reflection layer between profile generation and
affirmation. The coach reflects the person's profile back, and only when they
explicitly correct or add something does it propose revisions in their own
direction — never prescribing goals.
- ReflectionCoach service (app/services/reflector.py): Anthropic-backed,
returns {message, revisions, revision_note}; revisions filtered to the seven
editable prose fields (never triad/type); one-retry JSON handling.
- Endpoints (owner-scoped, 409 when locked): POST /discovery/profile/me/reflect
(opener + turns, applies revisions), GET .../reflection (dialogue),
GET .../revisions (iteration history). complete records an 'extraction'
revision; PATCH records 'manual_edit'.
- Models + migration 004: reflection_message (coach/person turns) and
profile_revision (snapshots: extraction | reflection | manual_edit) —
captures edits and iterations rather than overwriting.
- Frontend: reflect.html chat (coach/person bubbles, live profile summary that
refreshes on revision, affirm); linked from profile.html.
- Affirmation remains the existing confirm/lock.
Also refresh README for Phase 2 and for the HTTPS deployment
(https://impactflow.teamci.org:8011, OAUTH_REDIRECT_URI + COOKIE_SECURE notes).
Tests: 50 passing (added reflector unit tests and reflection endpoint tests;
run in-container).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,10 @@ If you need to explain this app in detail, use this mental model:
|
||||
While it is unlocked the user can revise their words with
|
||||
`PATCH /discovery/profile/me`, then lock it with
|
||||
`PUT /discovery/profile/me/confirm`.
|
||||
12. Optionally (Phase 2) the user opens `/static/reflect.html` and refines the
|
||||
profile through an AI-coach reflection loop (`POST /discovery/profile/me/reflect`)
|
||||
— the coach mirrors the profile back and applies the person's own
|
||||
corrections — before affirming with the same confirm/lock.
|
||||
|
||||
Machine-to-machine callers (e.g. the MCP server) skip the OAuth dance and
|
||||
authenticate with `X-API-Key: $IMPACTFLOW_API_KEY` instead. That header
|
||||
@@ -136,16 +140,21 @@ Important files:
|
||||
| `app/services/extractor.py` | Anthropic client wrapper, prompt, JSON parsing, retry logic |
|
||||
| `app/migration_bootstrap.py` | Stamps pre-Alembic SQLite DBs as revision `001` so `alembic upgrade head` succeeds on older local databases |
|
||||
| `app/static/discovery.html` | Browser-based seven-prompt flow |
|
||||
| `app/static/profile.html` | Browser-based profile display, edit, and confirm actions |
|
||||
| `app/static/profile.html` | Browser-based profile display, edit, and confirm actions; links to reflection |
|
||||
| `app/static/reflect.html` | Phase 2 AI-coach reflection chat (mirror loop, applies revisions, affirm) |
|
||||
| `app/static/auth.js` | Shared `authedFetch` helper: sends session cookies, silently refreshes on `401`, redirects to login |
|
||||
| `app/static/style.css` | Shared UI styling |
|
||||
| `app/services/reflector.py` | `ReflectionCoach`: Anthropic-backed mirror loop, JSON parsing, revision filtering |
|
||||
| `alembic/versions/001_initial.py` | Initial database schema migration |
|
||||
| `alembic/versions/002_add_auth.py` | Adds `users`, `refresh_tokens`, and `activity_log` tables |
|
||||
| `alembic/versions/003_add_goals.py` | Adds the goal columns to `discovery_conversation` and `discovery_profile` |
|
||||
| `alembic/versions/004_add_reflection.py` | Adds `reflection_message` and `profile_revision` tables (Phase 2) |
|
||||
| `tests/conftest.py` | Shared `app_client` fixture (isolated app + temp DB) |
|
||||
| `tests/test_extractor.py` | Unit tests for extraction plumbing, goals, and retry behavior |
|
||||
| `tests/test_reflector.py` | Unit tests for `ReflectionCoach` (mirror, revision filtering, retry) |
|
||||
| `tests/test_auth.py` | Tests for the dual-auth dependency (JWT + cookie + API key), token refresh/logout, admin enforcement, and domain allow-list |
|
||||
| `tests/test_profile_edit.py` | Tests for `PATCH /discovery/profile/me` (edit, partial update, lock/`409`) |
|
||||
| `tests/test_reflection.py` | Tests for the reflection endpoints (turns, applied revisions, lock/`409`, history) |
|
||||
| `tests/test_migration_bootstrap.py` | Unit tests for the pre-Alembic SQLite stamping helper |
|
||||
| `tests/test_static_discovery.py` | Guard tests for the static pages' cookie-session and edit contract |
|
||||
| `tests/fixtures/{gut,head,heart}_type_responses.json` | Synthetic seven-prompt responses used by extractor tests |
|
||||
@@ -171,9 +180,11 @@ every subsequent user defaults to `role=user`. Admin-only routes (e.g.
|
||||
|
||||
1. In <https://console.cloud.google.com> open APIs & Services → Credentials.
|
||||
2. Create an **OAuth 2.0 Client ID** of type **Web application**.
|
||||
3. Add authorized redirect URIs that match `OAUTH_REDIRECT_URI` in `.env`:
|
||||
- `http://localhost:8011/api/auth/callback` for local dev
|
||||
- `http://<deploy-host>:8011/api/auth/callback` for the deployed instance
|
||||
3. Add authorized redirect URIs that match `OAUTH_REDIRECT_URI` in `.env`
|
||||
exactly (scheme, host, port, path — Google treats `localhost` and
|
||||
`127.0.0.1` as distinct):
|
||||
- `http://localhost:8011/api/auth/callback` for local http dev (set `COOKIE_SECURE=false`)
|
||||
- `https://impactflow.teamci.org:8011/api/auth/callback` for the deployed instance (HTTPS; set `COOKIE_SECURE=true`)
|
||||
4. Copy the client id and client secret into `.env` as `GOOGLE_CLIENT_ID` and
|
||||
`GOOGLE_CLIENT_SECRET`.
|
||||
5. Generate two random 64-character strings and put them in `.env` as
|
||||
@@ -354,6 +365,27 @@ It rejects with:
|
||||
once locked, the profile can no longer be edited (`PATCH` returns `409`). It
|
||||
does not prevent future conversations from generating newer profiles.
|
||||
|
||||
### 8. Reflecting With The Coach (Phase 2)
|
||||
|
||||
The AI-coach reflection loop lets the person refine their profile through a
|
||||
conversation before affirming it. The coach is a **mirror, not a compass**: it
|
||||
reflects the profile back, asks whether it fits, and — only when the person
|
||||
explicitly corrects or adds something — proposes revised text in the person's
|
||||
own direction. It never prescribes goals or invents direction. Affirmation is
|
||||
still the `/confirm` lock above; the loop is what happens before it.
|
||||
|
||||
- `POST /discovery/profile/me/reflect` advances the loop by one turn. An empty
|
||||
`message` starts it (the coach's opening reflection); a non-empty `message`
|
||||
is recorded as the person's turn before the coach replies. When the coach
|
||||
proposes revisions, they are applied to the editable prose fields (never the
|
||||
structural Enneagram read) and snapshotted. Returns the coach turn, the
|
||||
(possibly revised) profile, and `revised`/`revision_note`. `409` once locked.
|
||||
- `GET /discovery/profile/me/reflection` returns the dialogue (ordered
|
||||
`coach`/`person` turns) plus the current profile.
|
||||
- `GET /discovery/profile/me/revisions` returns the profile's edit/iteration
|
||||
history, newest first. Every change is snapshotted in `profile_revision`
|
||||
with a `source` of `extraction` (initial), `reflection`, or `manual_edit`.
|
||||
|
||||
## API Reference
|
||||
|
||||
All `/discovery/*`, `/api/me*`, `/api/activity*`, and `/api/admin/*` routes
|
||||
@@ -385,6 +417,9 @@ clients.) `/api/auth/login`, `/api/auth/callback`, `/health`, `/`, and
|
||||
| `GET` | `/discovery/profile/me` | yes | Fetch newest profile for the authenticated user |
|
||||
| `PATCH` | `/discovery/profile/me` | yes | Edit the newest profile's prose (`409` if locked) |
|
||||
| `PUT` | `/discovery/profile/me/confirm` | yes | Lock newest profile for the authenticated user |
|
||||
| `POST` | `/discovery/profile/me/reflect` | yes | Advance the AI-coach reflection loop (Phase 2); applies revisions (`409` if locked) |
|
||||
| `GET` | `/discovery/profile/me/reflection` | yes | The reflection dialogue for the latest profile |
|
||||
| `GET` | `/discovery/profile/me/revisions` | yes | Profile edit/iteration history (newest first) |
|
||||
| `GET` | `/discovery/conversation/{conversation_id}` | yes | Fetch stored conversation responses (owner only) |
|
||||
|
||||
## Data Model
|
||||
@@ -477,6 +512,35 @@ Stores one extracted profile for one conversation.
|
||||
| `confidence_json` | text nullable | JSON string for confidence flags |
|
||||
| `locked` | boolean | Defaults to false |
|
||||
|
||||
### `reflection_message`
|
||||
|
||||
One turn in the Phase 2 AI-coach reflection loop (migration `004`).
|
||||
|
||||
| Column | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `id` | string | UUID primary key |
|
||||
| `profile_id` | string | FK to `discovery_profile.id`, indexed |
|
||||
| `user_id` | string | FK to `users.id`, indexed |
|
||||
| `role` | string | `coach` (AI mirror) or `person` (the human) |
|
||||
| `content` | text | The turn's text |
|
||||
| `sequence` | integer | Monotonic order within a profile's thread |
|
||||
| `created_at` | datetime | UTC |
|
||||
|
||||
### `profile_revision`
|
||||
|
||||
A snapshot of a profile's editable prose at a point in time, so edits and
|
||||
iterations are captured rather than overwritten (migration `004`).
|
||||
|
||||
| Column | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `id` | string | UUID primary key |
|
||||
| `profile_id` | string | FK to `discovery_profile.id`, indexed |
|
||||
| `user_id` | string | FK to `users.id`, indexed |
|
||||
| `source` | string | `extraction` (initial), `reflection`, or `manual_edit` |
|
||||
| `fields_json` | text | JSON snapshot of the seven editable prose fields |
|
||||
| `note` | text nullable | What changed (e.g. the coach's revision note) |
|
||||
| `created_at` | datetime | UTC |
|
||||
|
||||
## Extraction Details
|
||||
|
||||
`DiscoveryExtractor` is intentionally responsible for plumbing, not business
|
||||
@@ -550,6 +614,17 @@ callback), so the pages hold no tokens of their own.
|
||||
structural read (triad/type/wing/variant) is shown but not editable here
|
||||
- "This is me" locks the profile with `PUT /discovery/profile/me/confirm`; a
|
||||
locked profile shows the confirmed state and no longer offers edit
|
||||
- links to `reflect.html` ("talk it through with your coach") while unlocked
|
||||
|
||||
`reflect.html` (Phase 2 AI-coach reflection):
|
||||
|
||||
- loads the dialogue + profile via `GET /discovery/profile/me/reflection`, and
|
||||
auto-starts the coach's opening reflection when the thread is empty
|
||||
- shows a chat thread (coach / person bubbles) plus a live profile summary that
|
||||
refreshes when the coach applies a revision
|
||||
- each turn posts to `POST /discovery/profile/me/reflect`; when `revised` is
|
||||
true it updates the summary and notes what changed
|
||||
- "This is me — affirm" locks the profile via the same confirm endpoint
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -588,6 +663,26 @@ Environment variables:
|
||||
`app/database.py` creates the SQLite directory automatically when the URL uses
|
||||
a local SQLite file path.
|
||||
|
||||
### Production deployment
|
||||
|
||||
The service is deployed (Docker Compose on the `ci-gpu` host) behind TLS at:
|
||||
|
||||
```
|
||||
https://impactflow.teamci.org:8011
|
||||
```
|
||||
|
||||
The container reads its env from `.env` (`env_file` in `docker-compose.yml`),
|
||||
which is gitignored and lives only on the host — it does not travel with the
|
||||
repo. For this HTTPS deployment the env must set:
|
||||
|
||||
- `OAUTH_REDIRECT_URI=https://impactflow.teamci.org:8011/api/auth/callback`
|
||||
(and the same URI registered as an Authorized redirect URI on the Google
|
||||
OAuth client), and
|
||||
- `COOKIE_SECURE=true` so session cookies carry the `Secure` flag.
|
||||
|
||||
After changing `.env`, apply with `docker compose up -d` (recreates the
|
||||
container with the new env).
|
||||
|
||||
## Quick Start With Docker
|
||||
|
||||
This path requires Docker Desktop or another Docker Engine installation with
|
||||
|
||||
Reference in New Issue
Block a user