socratink-app is an MVP-stage learning product deployed on Vercel serverless.
The tracked repo contains the hosted product surface:
- the hosted app: FastAPI backend plus a vanilla JS frontend in
public/ - production Gemini prompt assets in
app_prompts/ - the app-local SEDA loop runtime used by learner concept sessions
The product doctrine is stable even while implementation is still moving:
- generation before recognition
- the graph tells the truth
- AI should remove prep friction and increase truthful retrieval reps
- local success does not validate hosted behavior
main.pyandapi/FastAPI app and Vercel entrypoint.ai_service.pyModel-facing extraction and drill logic.app_prompts/Production prompt assets bundled with the Vercel serverless function.public/Hosted frontend.lib/loop-public/Standalone/loopterminal UI. This is debug/backcompat; the normal learner product flow enters SEDA through the app shell and/api/session.lib/seda/App-local SEDA state machine, handlers, session rehydration, and evidence record projection.lib/loop-server/HTTP wrapper around the SEDA runtime.loop_backend_proxy.pystarts this vendored runtime locally with a file session store. On Vercel, FastAPI proxies learner sessions to a configured HTTPS loop service that can run both Node and the Python bridge. The service uses the authenticated user's Supabase token anddb/loop_sessions.sql; it never falls back to serverless temporary storage.bridge.py,bridge_lib/,vendor/python/Python bridge seam used by the loop runtime for model calls and fake-loop fixtures. These are app-local vendored files; they must not depend on a siblingsocratink-tui-agentcheckout.learning_cases/andpedagogical_agents/Promoted SEDA regression cases and agent contracts used by the runtime and lab tooling.
bash scripts/bootstrap-python.sh
bash scripts/dev.shThen open http://localhost:8000.
scripts/dev.sh binds Uvicorn to 127.0.0.1 by default (loopback-only).
For on-device mobile testing, override with HOST=0.0.0.0 bash scripts/dev.sh
so the dev server is also
reachable at http://<your-LAN-IP>:8000. The localhost auto-guest bypass stays
loopback-only: LAN/on-device requests use the normal login path unless routed
through a loopback tunnel.
scripts/dev.sh refuses to run without .venv/ (to avoid accidentally using
global/pyenv site-packages). It runs scripts/check-local-auth.py before
starting Uvicorn to catch missing .env / .env.local auth configuration.
End-to-End browser smoke tests are powered by Playwright and Pytest. To run them, make sure the development server is running in the background, or point the tests to the live production server.
# Test against local dev server (http://localhost:8000)
bash scripts/qa-smoke.sh local
# Test against the live production server (https://app.socratink.ai)
bash scripts/qa-smoke.sh liveChanged lines on this branch are required to be 100% covered across both the Python backend and the JavaScript frontend. The gate runs in CI and locally:
bash scripts/test-cov.sh # collect backend Python coverage with pytest-cov
bash scripts/check-coverage.sh # collect full-stack coverage and enforce 100% diff coveragescripts/check-coverage.sh calls the backend Python leg, generates frontend V8
coverage via scripts/generate-frontend-coverage.js, checks changed versioned
frontend assets with scripts/check_frontend_cache_pins.py, then runs
diff-cover against COMPARE_BRANCH when set or origin/main / main locally.
The default browser target is http://localhost:8000; for any
http://localhost:<port> or http://127.0.0.1:<port> target, the script
reuses a healthy app if one is already running, otherwise starts loopback
uvicorn on that port and writes its log to .qa-runs/check-coverage-uvicorn.log.
Set SOCRATINK_BASE_URL to a non-local target when the app should be provided
externally. The cache-pin check fails when a changed versioned frontend asset
keeps a stale parent ?v= reference. The frontend leg requires Node (run
npm install once to fetch the local Node tooling; coverage uses
monocart-coverage-reports). The Python leg adds pytest-cov and diff-cover
from requirements-dev.txt.
The type-check baseline is two-tool: pyrefly is the primary gate, mypy is
the cross-check. Both must be green and both run in scripts/doctor.sh and
in CI. Run both from the repo root before pushing:
.venv/bin/pyrefly check # honors project-includes in pyrefly.toml — do NOT pass `.`
mypy . # honors mypy.ini exclude list (.venv/, tests/e2e/, public/, scripts/, …)pyrefly.toml(Python 3.14,preset = "legacy",check-unannotated-defs = true) uses positiveproject-includesto mirror mypy's effective scope. Passingpyrefly check .would override that scope and pull intests/andapi/, both of which are intentionally excluded.- The pyrefly version is pinned in
scripts/doctor.sh(PYREFLY_VERSION), not inrequirements-dev.txt, so the gate auto-bootstraps the exact version it was authored against. mypy.ini(Python 3.14,warn_unreachable,strict_optional,check_untyped_defs,warn_return_any) stays the cross-check.
.github/workflows/preflight.yml runs two CI jobs on every pull_request and
on pushes to main/dev: the preflight job invokes bash scripts/doctor.sh
(which runs both checkers) plus pytest -q --ignore=tests/e2e, and the
coverage job installs Node/Chromium, starts a loopback app with
SOCRATINK_E2E_LOCAL_GUEST=1, selects COMPARE_BRANCH, and runs
scripts/check-coverage.sh. It is the public PR-time signal contributors will
see and is intentionally narrower than the local scripts/preflight-deploy.sh,
which additionally runs vercel build against real Vercel credentials and
stays local-only.
This repo keeps dependency management intentionally simple:
requirements.txtis the Vercel runtime install surface.requirements-dev.txtis local-only test and tooling surface (includespytest-covanddiff-coverfor the coverage gate).package.json/package-lock.jsoncarry local-only Node tooling for the frontend coverage gate (monocart-coverage-reports) and the repo-pinned Context Hub wrapper (@aisuite/chub); none of it ships to Vercel.- Keep the Python files flat: one pinned package per line, no
-rincludes, no hash blocks.
Before deploying hosted /api/session, deploy the vendored loop runtime to a
trusted HTTPS service that includes Node, Python and the bridge dependencies.
Set LOOP_BACKEND_URL and the same SOCRATINK_LOOP_API_KEY on the Vercel app
and loop service. Set SOCRATINK_LOOP_SESSION_STORE=supabase on the service.
Apply db/loop_sessions.sql to Supabase. The service also needs SUPABASE_URL
and SUPABASE_PUBLISHABLE_KEY.
FastAPI forwards the sealed user's access token only to that configured loop origin. The loop service uses it for row-level security. It rejects insecure origins, and Vercel fails closed when the URL or loop API key is missing. Do not configure a Supabase service-role or secret key for this path. The proxy rejects bodies over 64 KiB, performs blocking HTTP work off the FastAPI event loop, disables automatic retries for learner POSTs, and bounds upstream connect/read waits at 5/55 seconds.
Local development keeps the file store under
SOCRATINK_LOOP_SESSION_STORE_DIR (or the operating-system temporary default).
SOCRATINK_LOOP_SESSION_TTL_SECONDS may override the hosted 30-day session
expiry. Production and Vercel fail closed when durable storage is unavailable.
Schedule public.purge_expired_loop_sessions() once a day from a trusted
Supabase database schedule so expired rows do not accumulate.
This makes the loop event journal durable and account-scoped. Concepts and the
app-shell training record are still browser localStorage; full cross-device
learner continuity is not yet complete.
For source-less Door starts, /api/extract now returns only a deterministic
graph-neutral shell. It does not query Learning Commons or generate a route;
the typed SEDA sourceLessRoute is the single authoritative route owner. This
removes a duplicate model call from the first-session latency and cost path.
Dockerfile.loop packages the existing Node loop server with the repo-pinned
Python 3.14 bridge environment. requirements-loop.txt contains only the
bridge's direct Python dependencies and keeps their versions aligned with the
app runtime. The build context is allowlisted by
Dockerfile.loop.dockerignore, so local .env files and other workspace files
cannot enter the image.
docker build --file Dockerfile.loop --tag socratink-loop .
docker run --rm --publish 8787:8787 \
--env SOCRATINK_LOOP_API_KEY \
--env SUPABASE_URL \
--env SUPABASE_PUBLISHABLE_KEY \
--env GEMINI_API_KEY \
socratink-loopInject those four variables at runtime through the hosting platform's secret
store; do not pass secret values as Docker build arguments. The image sets
NODE_ENV=production, SOCRATINK_LOOP_SESSION_STORE=supabase,
HOST=0.0.0.0, and a default PORT=8787. A platform-provided PORT overrides
that default. Configure the platform's process health probe to GET /health.
That endpoint proves the process is up; an authenticated create-session smoke
test is still required to prove Supabase access, row ownership, and the live
model path.
After deployment, set the app's LOOP_BACKEND_URL to the service's HTTPS
origin and give the app the same SOCRATINK_LOOP_API_KEY. The service itself
does not need LOOP_BACKEND_URL. Optional runtime tuning such as
SOCRATINK_LOOP_SESSION_TTL_SECONDS, LLM_MODEL, and the bridge controls
remain environment-only. The bridge runs non-blocking child processes with
bounded defaults: SOCRATINK_BRIDGE_TIMEOUT_MS=45000,
SOCRATINK_BRIDGE_MAX_CONCURRENCY=4, SOCRATINK_BRIDGE_MAX_QUEUE=16, and
SOCRATINK_BRIDGE_MAX_OUTPUT_BYTES=1048576. Tune concurrency only after a
hosted load test confirms CPU, memory, model quota, and database behavior.
When preparing and validating a deployment, clearly distinguish between these stages:
- Local prerequisite validation:
bash scripts/doctor.sh
- Vercel build readiness:
This proves local Vercel build readiness, not hosted production correctness.
bash scripts/preflight-deploy.sh
- Hosted production validation after deployment:
bash scripts/verify-deploy.sh HEAD
Doc-only changes to this section still require bash scripts/doctor.sh, because agent and deploy instructions are part of the repository's executable workflow.