diff --git a/CLAUDE.md b/CLAUDE.md index df109e5d..ab997bb1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,64 @@ -# Compatibility shim +## Workflow Orchestration -This repository uses `AGENTS.md` as the canonical instruction file for all coding agents. +### 1. Plan Mode Default +- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions) +- If something goes sideways, STOP and re-plan immediately - don't keep pushing +- Use plan mode for verification steps, not just building +- Write detailed specs upfront to reduce ambiguity -If your tool auto-loads `CLAUDE.md`, apply the rules from `AGENTS.md`. +--- + +### 2. Subagent Strategy +- Use subagents liberally to keep main context window clean +- Offload research, exploration, and parallel analysis to subagents +- For complex problems, throw more compute at it via subagents +- One task per subagent for focused execution + +--- + +### 3. Self-Improvement Loop +- After ANY correction from the user: update `tasks/lessons.md` with the pattern +- Write rules for yourself that prevent the same mistake +- Ruthlessly iterate on these lessons until mistake rate drops +- Review lessons at session start for relevant project + +--- + +### 4. Verification Before Done +- Never mark a task complete without proving it works +- Diff behavior between main and your changes when relevant +- Ask yourself: "Would a staff engineer approve this?" +- Run tests, check logs, demonstrate correctness + +--- + +### 5. Demand Elegance (Balanced) +- For non-trivial changes: pause and ask "is there a more elegant way?" +- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution" +- Skip this for simple, obvious fixes - don't over-engineer +- Challenge your own work before presenting it + +--- + +### 6. Autonomous Bug Fixing +- When given a bug report: just fix it. Don't ask for hand-holding +- Point at logs, errors, failing tests - then resolve them +- Zero context switching required from the user +- Go fix failing CI tests without being told how + +--- + +## Task Management +1. **Plan First**: Write plan to `tasks/todo.md` with checkable items +2. **Verify Plan**: Check in before starting implementation +3. **Track Progress**: Mark items complete as you go +4. **Explain Changes**: High-level summary at each step +5. **Document Results**: Add review section to `tasks/todo.md` +6. **Capture Lessons**: Update `tasks/lessons.md` after corrections + +--- + +## Core Principles +- **Simplicity First**: Make every change as simple as possible. Impact minimal code +- **No Laziness**: Find root causes. No temporary fixes. Senior developer standards +- **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs diff --git a/backend/api/alignment.py b/backend/api/alignment.py new file mode 100644 index 00000000..0866ea97 --- /dev/null +++ b/backend/api/alignment.py @@ -0,0 +1,20 @@ +"""Alignment gate API — evaluate whether a concept fits the curriculum.""" + +from __future__ import annotations + +from fastapi import APIRouter +from pydantic import BaseModel, Field + +from backend.services.alignment_gate import evaluate + +router = APIRouter(prefix="/api/alignment", tags=["alignment"]) + + +class AlignmentRequest(BaseModel): + value: str = Field(..., min_length=1, max_length=200) + signal_type: str = Field(default="concept") + + +@router.post("/evaluate") +async def evaluate_alignment(body: AlignmentRequest): + return evaluate(body.value, body.signal_type) diff --git a/backend/api/career.py b/backend/api/career.py index 4d3c9bea..dfe17f17 100644 --- a/backend/api/career.py +++ b/backend/api/career.py @@ -22,7 +22,6 @@ JobRecommendationImpressionRequest, JobRecommendationImpressionResponse, JobRecommendationsResponse, - PatternsOverview, UserCareerGoal, UserCareerGoalResponse, ) @@ -40,7 +39,6 @@ from backend.services.career.goal import get_career_goal, upsert_career_goal from backend.services.career.inventory import get_career_taxonomy_inventory from backend.services.career.job_ingest import extract_job_from_url, save_job_to_corpus -from backend.services.career.patterns import get_patterns_overview from backend.services.career.recommendations import ( get_job_recommendation_feedback_stats, get_job_recommendations, @@ -98,12 +96,6 @@ def career_composites(): return get_composites_overview() -@router.get("/patterns", response_model=PatternsOverview) -def career_patterns(): - """Get enterprise pattern fit scores against user skill profile.""" - return get_patterns_overview() - - @router.get("/coaches", response_model=CoachRecommendationsResponse) async def career_coaches(): """Generate personalized coach recommendations based on career gaps.""" diff --git a/backend/api/concepts.py b/backend/api/concepts.py index 672437cc..045804c2 100644 --- a/backend/api/concepts.py +++ b/backend/api/concepts.py @@ -310,7 +310,7 @@ async def extract_atoms_endpoint(concept_id: str): @router.post("/refresh-counts") def refresh_counts(): - """Recompute source_count for all concepts from learning_concepts.""" + """Recompute source_count for all concepts from signals.""" updated = refresh_all_source_counts() return {"status": "ok", "updated": updated} diff --git a/backend/api/ingest.py b/backend/api/ingest.py index 94e7a73d..e4cafd20 100644 --- a/backend/api/ingest.py +++ b/backend/api/ingest.py @@ -41,7 +41,6 @@ IngestTraceEvent, IngestUrlRequest, IngestUrlResponse, - PatternImpactPreviewRequest, PolicyCriterionResponse, RelevanceGate, SignalPolicyResponse, @@ -143,6 +142,7 @@ async def run_ingestion(job_id: str, request: IngestRequest): ingest_article, ingest_file, ingest_youtube, + normalize_job_text, ) project_context = _resolve_project_context(request.destination) @@ -200,6 +200,29 @@ def on_progress(progress: int, step: str): chunks_created = result["chunks_created"] source_id = result.get("source_id", "") + elif content_type == ContentType.JOB: + import hashlib + + normalized_text = normalize_job_text(request.content) + job_hash = hashlib.sha256(normalized_text.encode()).hexdigest()[:12] + # Use provided URL (via manual_description) if available, else synthetic + provided_url = (request.manual_description or "").strip() + source_url = provided_url if provided_url.startswith("http") else f"job://{job_hash}" + result = await ingest_article( + url=source_url, + namespace=request.namespace, + on_progress=on_progress, + taxonomy_labels=request.taxonomy_labels, + job_id=job_id, + project_context=project_context, + manual_caption=normalized_text, + manual_title=request.manual_title or "Job Post", + source_type="job", + ) + title = result["title"] + chunks_created = result.get("chunks_created", 0) + source_id = result.get("source_id", "") + elif content_type == ContentType.PDF: from backend.services.ingest.steps.fetch import download_pdf @@ -371,6 +394,8 @@ async def get_activity_feed( ct = ContentType.YOUTUBE elif sid.startswith("article_"): ct = ContentType.ARTICLE + elif sid.startswith("job_"): + ct = ContentType.JOB else: ct = ContentType.TEXT @@ -562,26 +587,57 @@ async def get_signal_policy(): @router.post("/ingest", response_model=IngestJob) async def start_ingestion(request: IngestRequest, background_tasks: BackgroundTasks): """Start content ingestion (returns immediately, processing is async).""" + from backend.services.ingest.steps.fetch import ( + UnsupportedSourceError, + check_domain_policy, + ) + + # Block unsupported domains (e.g. LinkedIn) before any processing + try: + check_domain_policy(request.content.strip()) + except UnsupportedSourceError as e: + raise HTTPException(status_code=422, detail={"error": "unsupported_source", "message": str(e)}) + _prune_jobs() job_id = str(uuid.uuid4()) # Detect content type for initial response - detection = detect_content_type(request.content) + if request.content_type: + initial_content_type = request.content_type + else: + detection = detect_content_type(request.content) + initial_content_type = detection.content_type + + # For job posts, store the resolved URL (not the raw JD text) + if initial_content_type == ContentType.JOB: + import hashlib as _hl + + from backend.services.ingest.pipeline import normalize_job_text + + _norm = normalize_job_text(request.content) + _jhash = _hl.sha256(_norm.encode()).hexdigest()[:12] + _provided = (request.manual_description or "").strip() + initial_source_url = _provided if _provided.startswith("http") else f"job://{_jhash}" + else: + initial_source_url = request.content.strip() dest = request.destination job = IngestJob( job_id=job_id, status=IngestStatus.QUEUED, - content_type=detection.content_type, - title=request.transcript_title or request.manual_title or detection.metadata.get("video_id") or detection.metadata.get("url"), - source_url=request.content.strip(), + content_type=initial_content_type, + title=request.transcript_title or request.manual_title or request.content.strip()[:80], + source_url=initial_source_url, destination_type=dest.type if dest else "library", destination_project_id=dest.project_id if dest else None, created_at=datetime.now(UTC), ) - # Early dedup: reject if a completed job with the same URL already exists - existing_job = find_job_by_url(request.content.strip()) + # Early dedup: skip URL-based dedup for text-content types (JD text isn't a URL) + if initial_content_type not in (ContentType.JOB,): + existing_job = find_job_by_url(request.content.strip()) + else: + existing_job = None if existing_job and existing_job.status == IngestStatus.COMPLETED: raise HTTPException( status_code=409, @@ -622,7 +678,20 @@ async def batch_ingest(request: BatchIngestRequest, background_tasks: Background duplicates = 0 errors = 0 + from backend.services.ingest.steps.fetch import ( + UnsupportedSourceError, + check_domain_policy, + ) + for url in unique_urls: + # Block unsupported domains (e.g. LinkedIn) + try: + check_domain_policy(url) + except UnsupportedSourceError: + errors += 1 + items.append(BatchIngestItem(url=url, status="error", error="LinkedIn URLs are not supported. Paste the content manually.")) + continue + # Early dedup check — same as single ingest existing_job = find_job_by_url(url) if existing_job and existing_job.status == IngestStatus.COMPLETED and not request.force: @@ -805,9 +874,9 @@ async def get_maintenance_stats(): row = conn.execute( """SELECT COUNT(*) FILTER (WHERE confirmed_at IS NOT NULL) AS total_confirmed, - COUNT(*) FILTER (WHERE confirmed_at IS NOT NULL AND pattern_snapshot_json IS NOT NULL) AS with_snapshots, - COUNT(*) FILTER (WHERE confirmed_at IS NOT NULL AND pattern_snapshot_json IS NULL) AS missing_snapshots - FROM learnings""" + COUNT(*) FILTER (WHERE confirmed_at IS NOT NULL AND composite_snapshot_json IS NOT NULL) AS with_snapshots, + COUNT(*) FILTER (WHERE confirmed_at IS NOT NULL AND composite_snapshot_json IS NULL) AS missing_snapshots + FROM source_extractions""" ).fetchone() job_row = conn.execute( """SELECT @@ -1104,6 +1173,17 @@ async def ingest_enterprise( if not text.strip() and not source_url: raise HTTPException(status_code=400, detail="Provide text content or a source URL") + # Block LinkedIn URLs in enterprise mode too + if source_url: + from backend.services.ingest.steps.fetch import ( + UnsupportedSourceError, + check_domain_policy, + ) + try: + check_domain_policy(source_url) + except UnsupportedSourceError as e: + raise HTTPException(status_code=422, detail={"error": "unsupported_source", "message": str(e)}) + _prune_jobs() job_id = str(uuid.uuid4()) job = IngestJob( @@ -1314,12 +1394,23 @@ async def ingest_url(request: IngestUrlRequest): ingest_file, ingest_youtube, ) - from backend.services.ingest.steps.fetch import download_pdf, validate_url + from backend.services.ingest.steps.fetch import ( + UnsupportedSourceError, + check_domain_policy, + download_pdf, + validate_url, + ) url = request.url namespace = request.namespace project_context = _resolve_project_context(request.destination) + # Block unsupported domains (e.g. LinkedIn) + try: + check_domain_policy(url) + except UnsupportedSourceError as e: + return IngestUrlResponse(status="error", error=str(e)) + try: validate_url(url) except ValueError as e: @@ -1565,43 +1656,6 @@ async def upload_ingest_screenshots( return {"source_id": source_id, "saved": len(saved)} -@router.post("/ingest/preview-impact") -async def preview_pattern_impact_endpoint( - request: PatternImpactPreviewRequest, -): - """Preview how proposed skills from new content would change pattern fit.""" - from backend.services.career.patterns import preview_pattern_impact - - result = preview_pattern_impact( - proposed_concepts=request.proposed_concepts, - proposed_tools=request.proposed_tools, - exclude_source_id=request.exclude_source_id, - ) - return result - - -@router.get("/ingest/{job_id}/pattern-preview") -async def get_pattern_preview_for_job(job_id: str): - """Get pattern impact preview for a completed ingestion job. - - Resolves the source_id from the events table, then computes a - before/after diff by excluding that source from the baseline. - """ - from backend.services.career.patterns import preview_pattern_impact - from backend.services.chat.history import get_db as _get_db - - conn = _get_db() - row = conn.execute( - "SELECT DISTINCT source_id FROM ingestion_events WHERE job_id = ? LIMIT 1", - (job_id,), - ).fetchone() - - if not row: - raise HTTPException(status_code=404, detail="No source found for this job") - - result = preview_pattern_impact(exclude_source_id=row["source_id"]) - return result - @router.get("/ingest/{job_id}/trace", response_model=IngestTrace) async def get_ingest_trace(job_id: str): @@ -1610,7 +1664,6 @@ async def get_ingest_trace(job_id: str): from backend.services.career.assessment import match_user_skill_evidence from backend.services.career.composites import preview_composite_impact - from backend.services.career.patterns import preview_pattern_impact from backend.services.ingest.events import get_events_for_source from backend.services.ingest.learnings_store import get_learnings, get_pending @@ -1674,7 +1727,6 @@ async def get_ingest_trace(job_id: str): events=events, skills=skills, superpowers=preview_composite_impact(exclude_source_id=source_id), - patterns=preview_pattern_impact(exclude_source_id=source_id), ) @@ -1730,12 +1782,10 @@ async def confirm_learnings(job_id: str, request: ConfirmLearningsRequest): # Persist impact snapshots at confirmation time try: from backend.services.career.composites import preview_composite_impact - from backend.services.career.patterns import preview_pattern_impact from backend.services.ingest.learnings_store import save_impact_snapshots - pattern_snap = preview_pattern_impact(exclude_source_id=source_id) composite_snap = preview_composite_impact(exclude_source_id=source_id) - save_impact_snapshots(source_id, pattern_snap, composite_snap) + save_impact_snapshots(source_id, None, composite_snap) except Exception: logger.warning("Failed to persist impact snapshots for %s", source_id, exc_info=True) @@ -1750,15 +1800,14 @@ async def confirm_learnings(job_id: str, request: ConfirmLearningsRequest): async def backfill_snapshots(): """Recompute impact snapshots for all confirmed sources missing them.""" from backend.services.career.composites import preview_composite_impact - from backend.services.career.patterns import preview_pattern_impact from backend.services.chat.history import get_db as _get_db from backend.services.ingest.learnings_store import save_impact_snapshots conn = _get_db() rows = conn.execute( - """SELECT source_id FROM learnings + """SELECT source_id FROM source_extractions WHERE confirmed_at IS NOT NULL - AND pattern_snapshot_json IS NULL""" + AND composite_snapshot_json IS NULL""" ).fetchall() backfilled = 0 @@ -1766,9 +1815,8 @@ async def backfill_snapshots(): for row in rows: sid = row["source_id"] try: - pattern_snap = preview_pattern_impact(exclude_source_id=sid) composite_snap = preview_composite_impact(exclude_source_id=sid) - save_impact_snapshots(sid, pattern_snap, composite_snap) + save_impact_snapshots(sid, None, composite_snap) backfilled += 1 except Exception: logger.warning("Snapshot backfill failed for %s", sid, exc_info=True) @@ -1831,164 +1879,3 @@ async def websocket_progress(websocket: WebSocket, job_id: str): except WebSocketDisconnect: pass - - -# ---- Pattern Curation Loop endpoints ---- - - -@router.post("/patterns/curate") -async def curate_patterns(job_id: str = Query(...)): - """Trigger pattern clustering + diff computation for a confirmed job.""" - from backend.services.infrastructure.db import get_db as _get_db - from backend.services.ingest.learnings_store import get_confirmed_learnings - - conn = _get_db() - job_row = conn.execute( - "SELECT source_id FROM ingest_jobs WHERE job_id = ?", (job_id,) - ).fetchone() - if not job_row or not job_row["source_id"]: - raise HTTPException(404, "Job not found or no source_id") - - source_id = job_row["source_id"] - learnings = get_confirmed_learnings(source_id) - if not learnings: - raise HTTPException(400, "No confirmed learnings for this source") - - from backend.services.patterns.clustering import compute_pattern_diff - - diff = compute_pattern_diff( - job_id=job_id, - source_id=source_id, - confirmed_concepts=learnings.get("concepts", []), - confirmed_tools=learnings.get("tools", []), - ) - - return {"status": "ok", "diff": diff} - - -@router.get("/patterns/diff/{job_id}") -async def get_pattern_diff(job_id: str): - """Get stored pattern diff for a job.""" - import json as _json - - from backend.services.infrastructure.db import get_db as _get_db - - conn = _get_db() - row = conn.execute( - "SELECT diff_json, created_at FROM pattern_diffs WHERE job_id = ? ORDER BY id DESC LIMIT 1", - (job_id,), - ).fetchone() - - if not row: - return {"diff": None} - - return { - "diff": _json.loads(row["diff_json"]), - "created_at": row["created_at"], - } - - -@router.get("/patterns") -async def list_patterns(status: str = Query("all")): - """List all patterns with computed strength.""" - from backend.services.infrastructure.db import get_db as _get_db - from backend.services.patterns.clustering import get_pattern_strength - - conn = _get_db() - if status == "all": - rows = conn.execute("SELECT * FROM patterns ORDER BY name").fetchall() - else: - rows = conn.execute( - "SELECT * FROM patterns WHERE status = ? ORDER BY name", (status,) - ).fetchall() - - results = [] - for row in rows: - strength_info = get_pattern_strength(conn, row["id"]) - results.append({ - "id": row["id"], - "name": row["name"], - "description": row["description"], - "status": row["status"], - "source_type": row["source_type"], - "created_at": row["created_at"], - "confirmed_at": row["confirmed_at"], - **strength_info, - }) - - return {"patterns": results} - - -@router.get("/patterns/{pattern_id}") -async def get_pattern_detail(pattern_id: str): - """Get pattern detail with signals and computed strength.""" - from backend.services.infrastructure.db import get_db as _get_db - from backend.services.patterns.clustering import get_pattern_strength - - conn = _get_db() - row = conn.execute("SELECT * FROM patterns WHERE id = ?", (pattern_id,)).fetchone() - if not row: - raise HTTPException(404, "Pattern not found") - - signals = conn.execute( - "SELECT signal, signal_type, strength, source_id FROM pattern_signals WHERE pattern_id = ?", - (pattern_id,), - ).fetchall() - - strength_info = get_pattern_strength(conn, pattern_id) - - return { - "id": row["id"], - "name": row["name"], - "description": row["description"], - "status": row["status"], - "source_type": row["source_type"], - "created_at": row["created_at"], - "confirmed_at": row["confirmed_at"], - **strength_info, - "signals": [ - { - "signal": s["signal"], - "signal_type": s["signal_type"], - "strength": s["strength"], - "source_id": s["source_id"], - } - for s in signals - ], - } - - -@router.post("/patterns/{pattern_id}/confirm") -async def confirm_pattern(pattern_id: str): - """Confirm a proposed pattern.""" - from backend.services.infrastructure.db import get_db as _get_db - - conn = _get_db() - row = conn.execute("SELECT status FROM patterns WHERE id = ?", (pattern_id,)).fetchone() - if not row: - raise HTTPException(404, "Pattern not found") - - conn.execute( - "UPDATE patterns SET status = 'confirmed', confirmed_at = ? WHERE id = ?", - (datetime.now(UTC).isoformat(), pattern_id), - ) - conn.commit() - return {"status": "ok"} - - -@router.post("/patterns/{pattern_id}/ignore") -async def ignore_pattern(pattern_id: str): - """Ignore a proposed pattern.""" - from backend.services.infrastructure.db import get_db as _get_db - - conn = _get_db() - row = conn.execute("SELECT status FROM patterns WHERE id = ?", (pattern_id,)).fetchone() - if not row: - raise HTTPException(404, "Pattern not found") - - conn.execute( - "UPDATE patterns SET status = 'ignored' WHERE id = ?", - (pattern_id,), - ) - conn.commit() - return {"status": "ok"} diff --git a/backend/api/profile.py b/backend/api/profile.py index ad8a5ae0..33784160 100644 --- a/backend/api/profile.py +++ b/backend/api/profile.py @@ -140,26 +140,8 @@ def get_suggestions(): @router.post("/suggestions/{pattern_id}/accept", response_model=ProfileSkill, status_code=201) def accept_suggestion(pattern_id: str, label: str | None = Query(None)): - """Accept a pattern-based suggestion and add it to the profile.""" - from backend.services.chat.history import get_db - from backend.services.patterns.clustering import get_pattern_strength - - conn = get_db() - pattern = conn.execute("SELECT * FROM patterns WHERE id = ?", (pattern_id,)).fetchone() - if not pattern: - raise HTTPException(404, "Pattern not found") - - strength_info = get_pattern_strength(conn, pattern_id) - skill_name = label or pattern["name"] - - result = add_profile_skill( - skill_name=skill_name, - source="promoted_from_pattern", - visibility_status="visible", - pattern_id=pattern_id, - support_score=strength_info["strength"], - ) - return ProfileSkill(**result) + """Accept a suggestion and add it to the profile (pattern system removed).""" + raise HTTPException(410, "Pattern suggestion acceptance has been removed") # --------------------------------------------------------------------------- diff --git a/backend/api/profiles.py b/backend/api/profiles.py new file mode 100644 index 00000000..2f00f791 --- /dev/null +++ b/backend/api/profiles.py @@ -0,0 +1,55 @@ +"""Job profiles API — list profiles and compute concept coverage.""" + +from __future__ import annotations + +from fastapi import APIRouter, HTTPException + +from backend.services.chat.history import get_db +from backend.services.job_profiles import load_job_profiles + +router = APIRouter(prefix="/api/profiles", tags=["profiles"]) + + +@router.get("") +async def list_profiles(): + profiles = load_job_profiles() + result = [] + for name, profile in profiles.items(): + result.append({ + "name": name, + "description": profile["description"], + "core_concepts": profile["core_concepts"], + "tools": profile["tools"], + "infrastructure": profile["infrastructure"], + "signals_expected": profile["signals_expected"], + }) + return {"profiles": result} + + +@router.get("/{name}/coverage") +async def profile_coverage(name: str): + profiles = load_job_profiles() + profile = profiles.get(name) + if not profile: + raise HTTPException(status_code=404, detail=f"Profile '{name}' not found") + + required = profile["core_concepts"] + profile["infrastructure"] + + conn = get_db() + placeholders = ",".join("?" for _ in required) + rows = conn.execute( + f"SELECT id FROM concepts WHERE id IN ({placeholders})", + required, + ).fetchall() + matched = {r["id"] for r in rows} + + missing = [item for item in required if item not in matched] + coverage = len(matched) / len(required) if required else 0.0 + + return { + "name": name, + "required": required, + "matched": sorted(matched), + "missing": missing, + "coverage": round(coverage, 4), + } diff --git a/backend/api/signals.py b/backend/api/signals.py new file mode 100644 index 00000000..4ae72ab5 --- /dev/null +++ b/backend/api/signals.py @@ -0,0 +1,373 @@ +"""Signal Explorer API — endpoints for the V1 signal layer.""" + +from __future__ import annotations + +import logging +import re + +from fastapi import APIRouter, HTTPException, Query +from pydantic import BaseModel, Field + +from backend.services.chat.history import get_db + +logger = logging.getLogger(__name__) + +router = APIRouter(prefix="/api/signals", tags=["signals"]) + + +@router.get("") +async def list_signals( + status: str | None = Query(None), + signal_type: str | None = Query(None), + min_frequency: int = Query(0, ge=0), + source_id: str | None = Query(None), + limit: int = Query(200, ge=1, le=1000), + offset: int = Query(0, ge=0), +): + conn = get_db() + + where_clauses = [] + params: list[str | int] = [] + + if status: + where_clauses.append("s.status = ?") + params.append(status) + if signal_type: + where_clauses.append("s.signal_type = ?") + params.append(signal_type) + if source_id: + where_clauses.append("s.source_id = ?") + params.append(source_id) + + where_sql = (" AND ".join(where_clauses)) if where_clauses else "1=1" + + sql = f""" + SELECT + s.id, + s.source_id, + s.signal_type, + s.raw_value, + s.normalized_value, + s.status, + s.confidence, + s.created_at, + (SELECT COUNT(*) FROM signals s2 + WHERE s2.normalized_value = s.normalized_value + AND s2.signal_type = s.signal_type) AS frequency, + m.target_type, + m.target_id, + CASE + WHEN m.target_type = 'concept' THEN c.name + WHEN m.target_type = 'stack_item' THEN si.name + ELSE NULL + END AS target_name + FROM signals s + LEFT JOIN signal_mappings m ON m.signal_id = s.id + LEFT JOIN concepts c ON m.target_type = 'concept' AND m.target_id = c.id + LEFT JOIN stack_items si ON m.target_type = 'stack_item' AND m.target_id = si.id + WHERE {where_sql} + ORDER BY s.normalized_value, s.source_id + LIMIT ? OFFSET ? + """ + params.extend([limit, offset]) + + rows = conn.execute(sql, params).fetchall() + + # If min_frequency filter, post-filter (simpler than CTE for this size) + items = [] + for row in rows: + item = dict(row) + if min_frequency > 0 and item["frequency"] < min_frequency: + continue + items.append(item) + + # Total count for pagination + count_sql = f""" + SELECT COUNT(*) FROM signals s WHERE {where_sql} + """ + total = conn.execute(count_sql, params[:-2] if params else []).fetchone()[0] + + return {"signals": items, "total": total} + + +@router.get("/summary") +async def signal_summary(): + conn = get_db() + + by_status = conn.execute( + "SELECT status, COUNT(*) AS count FROM signals GROUP BY status ORDER BY status" + ).fetchall() + + by_type = conn.execute( + "SELECT signal_type, COUNT(*) AS count FROM signals GROUP BY signal_type ORDER BY signal_type" + ).fetchall() + + total_signals = conn.execute("SELECT COUNT(*) FROM signals").fetchone()[0] + total_mappings = conn.execute("SELECT COUNT(*) FROM signal_mappings").fetchone()[0] + total_stack_items = conn.execute("SELECT COUNT(*) FROM stack_items").fetchone()[0] + total_candidates = conn.execute("SELECT COUNT(*) FROM curriculum_candidates").fetchone()[0] + + return { + "total_signals": total_signals, + "total_mappings": total_mappings, + "total_stack_items": total_stack_items, + "total_candidates": total_candidates, + "by_status": [dict(r) for r in by_status], + "by_type": [dict(r) for r in by_type], + } + + +@router.get("/stack-items") +async def list_stack_items(): + conn = get_db() + rows = conn.execute( + "SELECT id, name, normalized_name, frequency, created_at FROM stack_items ORDER BY frequency DESC" + ).fetchall() + return {"stack_items": [dict(r) for r in rows], "total": len(rows)} + + +@router.get("/candidates") +async def list_candidates(): + conn = get_db() + rows = conn.execute( + "SELECT c.id, c.name, c.normalized_name, c.frequency, c.status, " + "COUNT(cs.signal_id) AS signals_count " + "FROM curriculum_candidates c " + "LEFT JOIN candidate_signals cs ON cs.candidate_id = c.id " + "GROUP BY c.id " + "ORDER BY c.frequency DESC, c.created_at DESC" + ).fetchall() + return {"candidates": [dict(r) for r in rows], "total": len(rows)} + + +@router.get("/by-source/{source_id}") +async def signals_by_source(source_id: str): + conn = get_db() + rows = conn.execute( + """SELECT + s.id, s.signal_type, s.raw_value, s.normalized_value, + s.status, s.confidence, + m.target_type, m.target_id, + CASE + WHEN m.target_type = 'concept' THEN c.name + WHEN m.target_type = 'stack_item' THEN si.name + ELSE NULL + END AS target_name + FROM signals s + LEFT JOIN signal_mappings m ON m.signal_id = s.id + LEFT JOIN concepts c ON m.target_type = 'concept' AND m.target_id = c.id + LEFT JOIN stack_items si ON m.target_type = 'stack_item' AND m.target_id = si.id + WHERE s.source_id = ? + ORDER BY s.signal_type, s.normalized_value""", + (source_id,), + ).fetchall() + return {"source_id": source_id, "signals": [dict(r) for r in rows], "total": len(rows)} + + +# --------------------------------------------------------------------------- +# Concept search (for the map dropdown) +# --------------------------------------------------------------------------- + +@router.get("/concepts/search") +async def search_concepts(q: str = Query("", min_length=0)): + conn = get_db() + if not q.strip(): + rows = conn.execute( + "SELECT id, name FROM concepts ORDER BY source_count DESC LIMIT 50" + ).fetchall() + else: + rows = conn.execute( + "SELECT id, name FROM concepts WHERE name LIKE ? ORDER BY source_count DESC LIMIT 50", + (f"%{q}%",), + ).fetchall() + return {"concepts": [{"id": r["id"], "name": r["name"]} for r in rows]} + + +# --------------------------------------------------------------------------- +# Mutation helpers +# --------------------------------------------------------------------------- + +def _get_signal_or_404(conn, signal_id: int) -> dict: + row = conn.execute("SELECT * FROM signals WHERE id = ?", (signal_id,)).fetchone() + if not row: + raise HTTPException(status_code=404, detail="Signal not found") + return dict(row) + + +def _normalize_to_id(value: str) -> str: + """Convert a value like 'React Query' to 'react-query'.""" + return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") + + +# --------------------------------------------------------------------------- +# Signal actions +# --------------------------------------------------------------------------- + +class MapRequest(BaseModel): + concept_id: str = Field(..., min_length=1) + + +@router.post("/{signal_id}/map") +async def map_signal(signal_id: int, body: MapRequest): + conn = get_db() + _get_signal_or_404(conn, signal_id) + + concept = conn.execute( + "SELECT id, name FROM concepts WHERE id = ?", (body.concept_id,) + ).fetchone() + if not concept: + raise HTTPException(status_code=404, detail="Concept not found") + + conn.execute( + "INSERT OR REPLACE INTO signal_mappings (signal_id, target_type, target_id) VALUES (?, 'concept', ?)", + (signal_id, body.concept_id), + ) + conn.execute("UPDATE signals SET status = 'mapped' WHERE id = ?", (signal_id,)) + conn.commit() + + return {"status": "mapped", "signal_id": signal_id, "target_name": concept["name"]} + + +@router.post("/{signal_id}/stack") +async def stack_signal(signal_id: int): + conn = get_db() + signal = _get_signal_or_404(conn, signal_id) + + stack_id = _normalize_to_id(signal["normalized_value"]) + + existing = conn.execute( + "SELECT id FROM stack_items WHERE id = ?", (stack_id,) + ).fetchone() + + if existing: + conn.execute( + "UPDATE stack_items SET frequency = frequency + 1 WHERE id = ?", + (stack_id,), + ) + else: + conn.execute( + "INSERT INTO stack_items (id, name, normalized_name, frequency) VALUES (?, ?, ?, 1)", + (stack_id, signal["raw_value"], signal["normalized_value"]), + ) + + conn.execute( + "INSERT OR REPLACE INTO signal_mappings (signal_id, target_type, target_id) VALUES (?, 'stack_item', ?)", + (signal_id, stack_id), + ) + conn.execute("UPDATE signals SET status = 'stacked' WHERE id = ?", (signal_id,)) + conn.commit() + + return {"status": "stacked", "signal_id": signal_id, "stack_item_id": stack_id} + + +@router.post("/{signal_id}/candidate") +async def candidate_signal(signal_id: int): + conn = get_db() + signal = _get_signal_or_404(conn, signal_id) + + normalized = signal["normalized_value"] + + existing = conn.execute( + "SELECT id FROM curriculum_candidates WHERE normalized_name = ?", + (normalized,), + ).fetchone() + + if existing: + candidate_id = existing["id"] + else: + cur = conn.execute( + "INSERT INTO curriculum_candidates (name, normalized_name, candidate_type, frequency, status) VALUES (?, ?, 'concept', 1, 'proposed')", + (signal["raw_value"], normalized), + ) + candidate_id = cur.lastrowid + + conn.execute( + "INSERT OR IGNORE INTO candidate_signals (candidate_id, signal_id) VALUES (?, ?)", + (candidate_id, signal_id), + ) + conn.commit() + + return {"status": "candidate", "signal_id": signal_id, "candidate_id": candidate_id} + + +@router.post("/candidates/{candidate_id}/promote") +async def promote_candidate(candidate_id: int): + conn = get_db() + + candidate = conn.execute( + "SELECT id, name, normalized_name, status FROM curriculum_candidates WHERE id = ?", + (candidate_id,), + ).fetchone() + if not candidate: + raise HTTPException(status_code=404, detail="Candidate not found") + + # Idempotent: already accepted → return the existing concept + if candidate["status"] == "accepted": + concept_id = _normalize_to_id(candidate["normalized_name"]) + mapped_count = conn.execute( + "SELECT COUNT(*) FROM signal_mappings sm " + "JOIN candidate_signals cs ON cs.signal_id = sm.signal_id " + "WHERE cs.candidate_id = ? AND sm.target_type = 'concept' AND sm.target_id = ?", + (candidate_id, concept_id), + ).fetchone()[0] + return {"concept_id": concept_id, "signals_mapped_count": mapped_count} + + concept_id = _normalize_to_id(candidate["normalized_name"]) + + # Fail if concept_id already exists (name collision with a different concept) + existing = conn.execute( + "SELECT id FROM concepts WHERE id = ?", (concept_id,) + ).fetchone() + if existing: + raise HTTPException( + status_code=409, + detail=f"Concept '{concept_id}' already exists", + ) + + # Insert concept + conn.execute( + "INSERT INTO concepts (id, name, source_count, created_at, updated_at) " + "VALUES (?, ?, 0, datetime('now'), datetime('now'))", + (concept_id, candidate["name"]), + ) + + # Link signals via signal_mappings + conn.execute( + "INSERT OR IGNORE INTO signal_mappings (signal_id, target_type, target_id) " + "SELECT signal_id, 'concept', ? FROM candidate_signals WHERE candidate_id = ?", + (concept_id, candidate_id), + ) + + # Update signal statuses to 'mapped' + conn.execute( + "UPDATE signals SET status = 'mapped' " + "WHERE id IN (SELECT signal_id FROM candidate_signals WHERE candidate_id = ?)", + (candidate_id,), + ) + + # Mark candidate accepted + conn.execute( + "UPDATE curriculum_candidates SET status = 'accepted' WHERE id = ?", + (candidate_id,), + ) + + conn.commit() + + mapped_count = conn.execute( + "SELECT COUNT(*) FROM candidate_signals WHERE candidate_id = ?", + (candidate_id,), + ).fetchone()[0] + + return {"concept_id": concept_id, "signals_mapped_count": mapped_count} + + +@router.post("/{signal_id}/discard") +async def discard_signal(signal_id: int): + conn = get_db() + _get_signal_or_404(conn, signal_id) + + conn.execute("UPDATE signals SET status = 'discarded' WHERE id = ?", (signal_id,)) + conn.execute("DELETE FROM signal_mappings WHERE signal_id = ?", (signal_id,)) + conn.commit() + + return {"status": "discarded", "signal_id": signal_id} diff --git a/backend/config/job_profiles.yaml b/backend/config/job_profiles.yaml new file mode 100644 index 00000000..c4b04b7d --- /dev/null +++ b/backend/config/job_profiles.yaml @@ -0,0 +1,121 @@ +version: 1 + +job_profiles: + + ml_platform_engineer: + description: Build and operate ML infrastructure at scale + core_concepts: + - ml-ops + - feature-store + - model-registry + - batch-vs-real-time-serving + - concept-drift + - experiment-tracking + - online-ab-testing + - feature-pipeline + - distributed-training + tools: + - python + - pytorch + - tensorflow + - kubernetes + - docker + - aws + - sagemaker + - airflow + - terraform + - grpc + - protobuf + infrastructure: + - infrastructure-automation + - training-reproducibility + - data-versioning + - model-performance-monitoring + - observability + signals_expected: + - "feature store" + - "model deployment" + - "ml pipeline" + - "drift" + - "experimentation" + - "distributed training" + + mlops_engineer: + description: Productionize ML systems reliably + core_concepts: + - deployment-strategy + - model-performance-monitoring + - data-validation + - dag-orchestration + - continual-learning + tools: + - kubernetes + - docker + - airflow + - mlflow + - aws + - gcp + infrastructure: + - observability + - alerting-thresholds + - model-rollback + - infrastructure-automation + - ci-cd + signals_expected: + - "model monitoring" + - "ci/cd" + - "model serving" + - "automation" + + data_engineer_modern: + description: Build scalable data pipelines for analytics & ML + core_concepts: + - data-ingestion + - data-contracts + - data-catalog + - batch-processing + - stream-processing + - data-lake + tools: + - sql + - dbt + - spark + - kafka + - airflow + - snowflake + - bigquery + infrastructure: + - data-warehouse + - orchestration + - scheduling + signals_expected: + - "pipeline" + - "etl" + - "data warehouse" + - "batch" + - "stream" + + applied_ml_engineer: + description: Build models and integrate into products + core_concepts: + - supervised-learning + - unsupervised-learning + - feature-engineering + - downstream-evaluation + - direct-preference-optimization + tools: + - python + - scikit-learn + - pytorch + - numpy + - pandas + infrastructure: + - inference-server + - latency-throughput-tradeoff + - batch-vs-realtime-inference + - rest-api + signals_expected: + - "model training" + - "training" + - "evaluation" + - "features" diff --git a/backend/jobs/backfill_concepts.py b/backend/jobs/backfill_concepts.py index 0966851c..5f1c3ce6 100644 --- a/backend/jobs/backfill_concepts.py +++ b/backend/jobs/backfill_concepts.py @@ -14,7 +14,7 @@ def backfill() -> dict: - """Canonicalize all confirmed learning_concepts into the concepts table. + """Canonicalize all confirmed concept signals into the concepts table. Returns dict with counts: sources_processed, concepts_created, concepts_existing. """ @@ -39,10 +39,10 @@ def backfill() -> dict: sources_processed = 0 for source_id in source_ids: rows = conn.execute( - "SELECT concept FROM learning_concepts WHERE source_id = ? ORDER BY position", + "SELECT raw_value FROM signals WHERE source_id = ? AND source_type = 'learning' AND signal_type = 'concept' ORDER BY id", (source_id,), ).fetchall() - concepts = [r["concept"] for r in rows] + concepts = [r["raw_value"] for r in rows] if concepts: canonicalize_concepts(source_id, concepts) sources_processed += 1 diff --git a/backend/jobs/backfill_signals.py b/backend/jobs/backfill_signals.py new file mode 100644 index 00000000..816b5d34 --- /dev/null +++ b/backend/jobs/backfill_signals.py @@ -0,0 +1,254 @@ +"""V1 Signal Layer — mapping pass and candidate generation for signals. + +Signals are now populated by dual-write in learnings_store.py. +This job handles the mapping + candidate generation passes. + +Idempotent: safe to re-run. + +Usage: + python -m backend.jobs.backfill_signals # dry run + python -m backend.jobs.backfill_signals --execute # commit changes +""" + +from __future__ import annotations + +import argparse +import logging +import sqlite3 +import sys +from dataclasses import dataclass + +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") +logger = logging.getLogger(__name__) + + +@dataclass +class Stats: + signals_created: int = 0 + mapped_to_concepts: int = 0 + mapped_to_stack: int = 0 + candidates_created: int = 0 + discarded: int = 0 + + +# --------------------------------------------------------------------------- +# Step 2 — Signals populated by dual-write (no-op backfill) +# --------------------------------------------------------------------------- + +def _backfill_signals(conn: sqlite3.Connection) -> int: + """No-op: signals are populated by writes in learnings_store.py.""" + total = conn.execute("SELECT COUNT(*) FROM signals").fetchone()[0] + logger.info("Signals table already has %d rows (populated by writes)", total) + return 0 + + +# --------------------------------------------------------------------------- +# Step 3 — Mapping pass (deterministic) +# --------------------------------------------------------------------------- + +def _mapping_pass(conn: sqlite3.Connection) -> tuple[int, int]: + """Map signals to existing concepts or create stack_items for frequent tools.""" + mapped_concepts = 0 + mapped_stack = 0 + + # --- 3a: Match against concepts.id or concept_aliases.alias --- + pending = conn.execute( + "SELECT id, normalized_value, signal_type FROM signals WHERE status = 'pending'" + ).fetchall() + + for signal_id, norm_val, signal_type in pending: + # Direct match on concepts.id + concept = conn.execute( + "SELECT id FROM concepts WHERE id = ?", (norm_val,) + ).fetchone() + + # Fallback: match on concept_aliases + if not concept: + concept = conn.execute( + "SELECT concept_id AS id FROM concept_aliases WHERE LOWER(alias) = ?", + (norm_val,), + ).fetchone() + + # Fallback: case-insensitive name match + if not concept: + concept = conn.execute( + "SELECT id FROM concepts WHERE LOWER(name) = ?", (norm_val,) + ).fetchone() + + if concept: + conn.execute( + """INSERT OR IGNORE INTO signal_mappings (signal_id, target_type, target_id) + VALUES (?, 'concept', ?)""", + (signal_id, concept[0]), + ) + conn.execute( + "UPDATE signals SET status = 'mapped' WHERE id = ? AND status = 'pending'", + (signal_id,), + ) + mapped_concepts += 1 + continue + + # --- 3b: Tools appearing >= 3 times → stack_items --- + freq_tools = conn.execute( + """SELECT normalized_value, COUNT(*) AS cnt + FROM signals + WHERE status = 'pending' AND signal_type = 'tool' + GROUP BY normalized_value + HAVING cnt >= 3""" + ).fetchall() + + for norm_val, freq in freq_tools: + # Create stack_item if not exists + stack_id = norm_val.replace(" ", "-") + conn.execute( + """INSERT OR IGNORE INTO stack_items (id, name, normalized_name, frequency) + VALUES (?, ?, ?, ?)""", + (stack_id, norm_val, norm_val, freq), + ) + # Update frequency if it already existed + conn.execute( + "UPDATE stack_items SET frequency = ? WHERE id = ?", + (freq, stack_id), + ) + + # Map all pending signals with this value + matching = conn.execute( + """SELECT id FROM signals + WHERE normalized_value = ? AND signal_type = 'tool' AND status = 'pending'""", + (norm_val,), + ).fetchall() + for (signal_id,) in matching: + conn.execute( + """INSERT OR IGNORE INTO signal_mappings (signal_id, target_type, target_id) + VALUES (?, 'stack_item', ?)""", + (signal_id, stack_id), + ) + conn.execute( + "UPDATE signals SET status = 'stacked' WHERE id = ? AND status = 'pending'", + (signal_id,), + ) + mapped_stack += 1 + + return mapped_concepts, mapped_stack + + +# --------------------------------------------------------------------------- +# Step 4 — Candidate generation +# --------------------------------------------------------------------------- + +def _generate_candidates(conn: sqlite3.Connection) -> tuple[int, int]: + """Promote frequent pending signals to candidates, discard the rest.""" + candidates_created = 0 + discarded = 0 + + # Group remaining pending signals by normalized_value + freq = conn.execute( + """SELECT normalized_value, COUNT(*) AS cnt + FROM signals + WHERE status = 'pending' + GROUP BY normalized_value""" + ).fetchall() + + for norm_val, cnt in freq: + if cnt >= 3: + # Create curriculum_candidate + r = conn.execute( + """INSERT OR IGNORE INTO curriculum_candidates + (name, normalized_name, candidate_type, frequency, status) + VALUES (?, ?, 'concept', ?, 'proposed')""", + (norm_val, norm_val, cnt), + ) + if r.rowcount == 0: + # Already exists — update frequency + conn.execute( + "UPDATE curriculum_candidates SET frequency = ? WHERE normalized_name = ?", + (cnt, norm_val), + ) + + cand_id = conn.execute( + "SELECT id FROM curriculum_candidates WHERE normalized_name = ?", + (norm_val,), + ).fetchone()[0] + + # Link signals + matching = conn.execute( + "SELECT id FROM signals WHERE normalized_value = ? AND status = 'pending'", + (norm_val,), + ).fetchall() + for (signal_id,) in matching: + conn.execute( + "INSERT OR IGNORE INTO candidate_signals (candidate_id, signal_id) VALUES (?, ?)", + (cand_id, signal_id), + ) + + candidates_created += 1 + else: + # Discard low-frequency signals + conn.execute( + "UPDATE signals SET status = 'discarded' WHERE normalized_value = ? AND status = 'pending'", + (norm_val,), + ) + discarded += cnt + + return candidates_created, discarded + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def run(execute: bool = False) -> Stats: + from backend.services.chat.history import get_db, init_db + + init_db() + conn = get_db() + + stats = Stats() + + # Step 2 + stats.signals_created = _backfill_signals(conn) + logger.info("Signals created: %d", stats.signals_created) + + # Step 3 + stats.mapped_to_concepts, stats.mapped_to_stack = _mapping_pass(conn) + logger.info("Mapped to concepts: %d", stats.mapped_to_concepts) + logger.info("Mapped to stack_items: %d", stats.mapped_to_stack) + + # Step 4 + stats.candidates_created, stats.discarded = _generate_candidates(conn) + logger.info("Candidates created: %d", stats.candidates_created) + logger.info("Discarded: %d", stats.discarded) + + # Step 5 — Summary + total_signals = conn.execute("SELECT COUNT(*) FROM signals").fetchone()[0] + by_status = conn.execute( + "SELECT status, COUNT(*) FROM signals GROUP BY status ORDER BY status" + ).fetchall() + total_mappings = conn.execute("SELECT COUNT(*) FROM signal_mappings").fetchone()[0] + total_stack = conn.execute("SELECT COUNT(*) FROM stack_items").fetchone()[0] + total_candidates = conn.execute("SELECT COUNT(*) FROM curriculum_candidates").fetchone()[0] + + logger.info("--- SUMMARY ---") + logger.info("Total signals: %d", total_signals) + for status, count in by_status: + logger.info(" %-20s %d", status, count) + logger.info("Signal mappings: %d", total_mappings) + logger.info("Stack items: %d", total_stack) + logger.info("Curriculum candidates: %d", total_candidates) + + if execute: + conn.commit() + logger.info("Changes committed.") + else: + conn.rollback() + logger.info("Dry run — rolled back. Use --execute to commit.") + + return stats + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Backfill V1 signal layer") + parser.add_argument("--execute", action="store_true", help="Commit changes (default is dry run)") + args = parser.parse_args() + stats = run(execute=args.execute) + sys.exit(0) diff --git a/backend/jobs/seed_ai_engineering_concepts.py b/backend/jobs/seed_ai_engineering_concepts.py new file mode 100644 index 00000000..0d21c5b9 --- /dev/null +++ b/backend/jobs/seed_ai_engineering_concepts.py @@ -0,0 +1,498 @@ +"""Bootstrap AI Engineering concepts and dossiers. + +Inserts concept rows + dossiers for concept-backed modules in the +AI Engineering track so the curriculum linker resolves titles and +readiness correctly. + +Usage: + python -m backend.jobs.seed_ai_engineering_concepts + python -m backend.jobs.seed_ai_engineering_concepts --dry-run +""" + +import argparse +import json +import logging +import sys + +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") +logger = logging.getLogger(__name__) + +SRC = "curriculum:ai-engineering-v1" + +# (id, name, description, lens, level, intuition, formula, insight) +CONCEPTS = [ + # Module 1: LLM Foundations + ( + "transformer-inference", + "Transformer Inference", + "The process of running a trained transformer model to generate output tokens - including the prefill phase (processing the prompt in parallel) and the decode phase (generating tokens one at a time), with KV-cache reuse to avoid redundant computation.", + "ai_engineering", + "university", + "Reading a book to answer a question: first you scan the whole passage (prefill), then you write your answer one word at a time (decode), keeping notes on what you already read so you don't re-read the passage for every word.", + None, + "Inference has two phases with very different hardware profiles. Prefill is compute-bound (matrix multiplications over the full prompt) and parallelizes well across GPUs. Decode is memory-bound (one token at a time, reading the full KV-cache each step). KV-cache stores the key/value tensors from previous tokens so they aren't recomputed - this is why GPU memory, not compute, is usually the bottleneck for long sequences. Continuous batching (vLLM, TensorRT-LLM) lets new requests join mid-batch instead of waiting for the longest sequence to finish. Speculative decoding uses a small draft model to propose multiple tokens, verified in parallel by the large model - trading compute for latency.", + ), + ( + "quantization", + "Quantization", + "Reducing the numerical precision of model weights and activations (e.g., from FP16 to INT4) to shrink memory footprint and increase throughput, with controlled accuracy loss.", + "ai_engineering", + "university", + "Compressing a high-resolution photo to a smaller file size - you lose some fine detail, but the image is still recognizable and now fits on your phone. The trick is knowing how much compression is too much.", + None, + "Post-training quantization (PTQ) converts a trained model without retraining - GPTQ and AWQ are the standard methods. QLoRA combines 4-bit quantization with LoRA adapters for fine-tuning on a single consumer GPU. Quantization levels have a practical hierarchy: FP16 (baseline), INT8 (2x compression, negligible quality loss), INT4 (4x compression, small quality loss on most tasks), lower than INT4 (significant degradation on reasoning). GGUF format (llama.cpp) offers granular quant levels like Q4_K_M and Q5_K_S for CPU/edge deployment. Always benchmark quantized models on YOUR task - aggregate benchmarks hide domain-specific degradation.", + ), + ( + "fine-tuning", + "Fine-Tuning", + "Adapting a pre-trained foundation model to a specific task or domain by continuing training on curated data, using either full-weight updates or parameter-efficient methods like LoRA that modify only a small subset of weights.", + "ai_engineering", + "university", + "A chef trained in French cuisine spending a month in a Japanese kitchen - they don't forget how to cook, but they learn new techniques and flavor profiles that reshape how they approach every dish.", + None, + "Parameter-efficient fine-tuning (PEFT) is the default starting point. LoRA adds small trainable rank-decomposition matrices to attention layers - typically 0.1-1% of total parameters. QLoRA combines LoRA with 4-bit quantization, enabling fine-tuning of 70B models on a single 24GB GPU. Full fine-tuning is only justified when you need deep behavioral changes across the entire model. Data quality dominates: 1,000 high-quality examples often outperform 100,000 noisy ones. Always hold out a test set from the same distribution as your training data. Instruction tuning (fine-tuning on instruction/response pairs) is what turns a base model into a useful assistant.", + ), + ( + "alignment", + "Alignment", + "The set of techniques applied after pre-training to make a model helpful, honest, and safe - including reinforcement learning from human feedback (RLHF), direct preference optimization (DPO), and constitutional AI methods.", + "ai_engineering", + "university", + "Training a new employee: they already know how to do the work (pre-training), but they need to learn the company's values, communication style, and what topics to escalate vs handle themselves.", + None, + "RLHF is the original recipe: collect human preference rankings on model outputs, train a reward model on those rankings, then optimize the language model against the reward model using PPO. DPO simplifies this by skipping the reward model entirely - it optimizes the policy directly from preference pairs, making it cheaper and more stable. Constitutional AI (Anthropic) replaces human labelers with a set of written principles - the model critiques and revises its own outputs against these principles. In practice, alignment is a multi-stage pipeline: supervised fine-tuning first (SFT on curated demonstrations), then preference optimization (RLHF or DPO). The core tradeoff is helpfulness vs safety - over-aligning makes models refuse legitimate requests.", + ), + ( + "multimodal-models", + "Multimodal Models", + "Foundation models that natively process and generate multiple data types - text, images, audio, video - in a unified architecture, enabling cross-modal reasoning without separate pipelines for each modality.", + "ai_engineering", + "university", + "A person who can read, listen, and look at diagrams all at once to answer a question - instead of separate specialists for text, images, and audio who need a coordinator to combine their answers.", + None, + "GPT-4o and Gemini are natively multimodal: all modalities share a single transformer with modality-specific tokenizers. This enables cross-modal reasoning (describing what's in an image, answering questions about a video frame). Vision inputs use patch embeddings - the image is cut into fixed-size patches, each projected into the same embedding space as text tokens. Audio follows a similar pattern with spectrogram patches. The practical impact: multimodal models eliminate the need for separate OCR, speech-to-text, or image captioning pipelines. Long-context multimodal (Gemini's 1M+ tokens) changes retrieval tradeoffs - you can process entire documents with images in a single call instead of building a RAG pipeline. Cost scales with token count across all modalities, so image-heavy workloads can be expensive.", + ), + # Module 2: Prompting & Control + ( + "prompt-structure", + "Prompt Structure", + "The deliberate arrangement of a prompt into distinct sections - system instructions, context, examples, and task - so the model treats each part with appropriate weight and follows the intended control flow.", + "ai_engineering", + "university", + "Writing a brief for a contractor: role description at the top, background materials in the middle, specific deliverable at the bottom. The order and labeling tell them what's context vs what's the actual ask.", + None, + "System/user/assistant role separation is the first structural lever - system instructions persist across turns, user messages carry the task. Few-shot examples (input/output pairs) are more reliable than verbose instructions for format control. XML tags or markdown headers inside prompts create parseable sections the model respects. Structure is not decoration - a well-structured prompt with a mediocre model often beats a flat prompt with a stronger model.", + ), + ( + "context-window-management", + "Context Window Management", + "Strategies for fitting the right information into a model's finite context window - prioritizing, compressing, and ordering content so the most task-relevant material occupies the highest-attention positions.", + "ai_engineering", + "university", + "Packing a carry-on bag for a trip: you can't bring everything, so you pick the essentials, compress bulky items, and put what you'll need first on top.", + None, + "Models attend unevenly: instructions at the start and end of the context get more weight than the middle ('lost in the middle' effect). For long contexts, place the most critical information at the boundaries. Summarization and compression (distilling prior turns into a summary) reclaim token budget without losing continuity. Context window size is a hard constraint - exceeding it silently truncates. Always track token counts programmatically, never estimate by eye.", + ), + ( + "self-consistency", + "Self-Consistency", + "A decoding strategy that samples multiple independent reasoning paths from the same prompt, then aggregates the final answers (typically by majority vote) to reduce variance and improve accuracy.", + "ai_engineering", + "university", + "Asking five people to solve the same math problem independently, then going with the answer most of them agree on. Any individual might make an error, but the consensus is usually right.", + None, + "Self-consistency is the simplest test-time compute scaling technique: spend N times the tokens, get measurably better answers on reasoning tasks. It works because LLM errors are partially random - different samples make different mistakes. Temperature > 0 is required (greedy decoding gives N identical answers). Diminishing returns kick in around N=5-10 for most tasks. Combine with chain-of-thought prompting for maximum effect. Cost scales linearly with N, so use it selectively on high-stakes outputs, not every API call.", + ), + ( + "prompt-evaluation", + "Prompt Evaluation", + "Systematically measuring prompt quality by running candidate prompts against a test dataset with defined assertions, comparing outputs across model versions, and tracking regressions before deployment.", + "ai_engineering", + "university", + "A/B testing for prompts: instead of guessing which wording works better, you run both versions against 50 test cases and measure which one scores higher on accuracy, format compliance, and relevance.", + None, + "Prompt changes are code changes - they need testing before deployment. Promptfoo and Braintrust both support assertion-based eval: define expected behaviors (contains keyword, matches regex, passes LLM-as-judge rubric), run the prompt against a dataset, get a pass/fail report. Golden datasets of 20-50 cases covering happy paths and edge cases are the minimum. Track eval scores over time - a prompt that scored 92% last week and 85% today is a regression, even if individual outputs look fine. Never ship a prompt change without a before/after comparison.", + ), + ( + "control-surface", + "Control Surface", + "The set of parameters and techniques available to steer LLM behavior beyond the prompt text itself - temperature, top-p, stop sequences, logit bias, forced tool use, and response format constraints.", + "ai_engineering", + "university", + "The knobs and dials on a mixing console: the prompt is the song, but temperature, top-p, and stop sequences let you control the dynamics, tone, and where the music cuts off.", + None, + "Temperature controls randomness (0 = deterministic, 1+ = creative). Top-p (nucleus sampling) caps cumulative probability of sampled tokens - more principled than temperature alone. Stop sequences terminate generation at defined markers (useful for structured output). Logit bias boosts or suppresses specific tokens by ID. Forced tool use (tool_choice) guarantees the model calls a tool instead of generating text. Response format (JSON mode, schema-strict mode) constrains output structure at the API level. These controls compose: low temperature + forced tool use + JSON mode gives highly predictable, structured outputs.", + ), + # Module 3: RAG + ( + "chunking", + "Chunking", + "Splitting documents into smaller passages so each piece fits a model's context window and carries a single coherent idea.", + "ai_engineering", + "university", + "Cutting a textbook into index cards - each card holds one idea, small enough to find quickly but big enough to make sense on its own.", + None, + "Chunk boundaries determine retrieval quality. Overlap prevents lost context at edges. Semantic chunking (split on topic shifts) beats fixed-size splits for most domains.", + ), + ( + "embeddings", + "Embeddings", + "Dense vector representations that encode semantic meaning, allowing text similarity to be measured as geometric distance.", + "ai_engineering", + "university", + "Words and passages become points in space - similar meanings land close together, unrelated ones land far apart.", + "embed(text) -> R^d; similarity = cos(u, v)", + "Model choice matters: general-purpose (OpenAI, Cohere) vs domain-fine-tuned. Embedding dimension affects storage cost and recall. Always normalize vectors for cosine similarity.", + ), + ( + "vector-search", + "Vector Search", + "Finding the nearest neighbors to a query embedding in a high-dimensional vector index using approximate nearest neighbor algorithms.", + "ai_engineering", + "university", + "Looking up a book by what it's about instead of by title - describe the topic and the library returns the closest matches.", + None, + "ANN indexes (HNSW, IVF) trade small accuracy for massive speed gains. Metadata filters narrow the search space before vector comparison. Always measure recall@k, not just latency.", + ), + ( + "hybrid-search", + "Hybrid Search", + "Combining dense vector similarity with sparse keyword matching (BM25/FTS) to cover both semantic and lexical relevance.", + "ai_engineering", + "university", + "Using both a GPS (semantic) and street signs (keywords) to navigate - GPS gets you to the neighborhood, street signs get you to the exact address.", + None, + "Reciprocal Rank Fusion (RRF) is the simplest merge strategy. Dense retrieval misses exact terms; sparse retrieval misses paraphrases. The combination consistently outperforms either alone.", + ), + ( + "reranking", + "Reranking", + "A second-stage scoring pass using a cross-encoder model that jointly processes query and document for more accurate relevance scoring than first-stage retrieval.", + "ai_engineering", + "university", + "First pass grabs 50 candidates quickly. Second pass reads each one carefully against the question and re-sorts by actual relevance.", + None, + "Cross-encoders are too slow for full-corpus search but dramatically improve precision on a shortlist. Cohere Rerank, ColBERT late interaction, and BGE-reranker are production options. Always retrieve more than you need, then rerank down.", + ), + # Module 4: Agents & Orchestration + ( + "agent-loop", + "Agent Loop", + "The observe-think-act cycle where an LLM repeatedly reads state, decides the next action, executes a tool call, and loops until the task is complete or a stop condition is met.", + "ai_engineering", + "university", + "A chef working through a recipe: read the next step, do it, check the result, decide what's next - repeat until the dish is done.", + None, + "The loop needs three exit conditions: task success, max iterations (safety bound), and explicit failure. Without a hard iteration cap, agents burn tokens on dead-end loops. Streaming intermediate steps gives users confidence the agent isn't stuck.", + ), + ( + "workflow-orchestration", + "Workflow Orchestration", + "Structuring multi-step LLM workflows as explicit graphs or state machines where nodes are processing steps and edges encode control flow, branching, and cycles.", + "ai_engineering", + "university", + "An airport departure board - every flight follows a fixed sequence (check-in, security, gate, board) but delays or cancellations reroute you through a known fallback path.", + None, + "Explicit graphs (LangGraph, Prefect) beat implicit chains because you can visualize, test, and replay individual nodes. State machines make failure recovery deterministic - when a node fails, the graph knows exactly where to retry. Keep nodes stateless; pass state through the graph context.", + ), + ( + "multi-agent-coordination", + "Multi-Agent Coordination", + "Running multiple specialized LLM agents that divide a complex task, work in parallel or sequence, and merge their results through a defined coordination protocol.", + "ai_engineering", + "university", + "A film crew: the director, cinematographer, and editor each have their own expertise. They work on different parts of the production but sync up at defined handoff points.", + None, + "Start with a single agent and split only when the task genuinely requires different system prompts, tools, or models. Most 'multi-agent' systems are better served by one agent with good tool selection. When you do split, shared state format is the contract - define it before writing any agent logic.", + ), + ( + "handoffs", + "Handoffs", + "The protocol by which one agent transfers control, conversation context, and partial results to another agent, including what state to pass and what to summarize.", + "ai_engineering", + "university", + "A hospital shift change: the outgoing doctor briefs the incoming one on each patient - what was done, what's pending, and what to watch for.", + None, + "A handoff is a function call with a defined schema, not a free-text summary. Include: task description, completed steps, accumulated state, and the specific next action expected. OpenAI Agents SDK and LangGraph both formalize this as typed transfer objects. Lossy handoffs (just passing the last message) are the #1 cause of multi-agent failures.", + ), + ( + "checkpointing", + "Checkpointing", + "Persisting agent state at defined points during execution so that a workflow can resume from the last checkpoint after a crash, timeout, or human-in-the-loop pause.", + "ai_engineering", + "university", + "A video game save point - if you die, you restart from the last save, not from the beginning of the level.", + None, + "Checkpoint granularity is the core tradeoff: per-step saves maximize resumability but add latency; per-phase saves are faster but lose more progress on failure. LangGraph persists full graph state to SQLite/Postgres. For human-in-the-loop, the checkpoint IS the approval gate - the workflow blocks until a human resumes it.", + ), + # Module 5: Memory & State + ( + "memory-extraction", + "Memory Extraction", + "An LLM-powered pipeline that reads raw conversations or documents and produces structured memory entries - facts, preferences, decisions - tagged with metadata and confidence scores.", + "ai_engineering", + "university", + "A personal assistant who listens to your meetings and writes down the key decisions, action items, and preferences in a structured notebook - not a transcript, just the things worth remembering.", + None, + "Extraction quality is the ceiling for all downstream memory operations. The extractor must decide what is memorable (not everything is) and normalize it into a schema (subject, predicate, object, confidence, timestamp). Mem0 and Zep both use LLM-based extraction with deduplication and conflict resolution. Run extraction asynchronously after each conversation turn - never block the response on memory writes.", + ), + ( + "episodic-vs-semantic-memory", + "Episodic vs Semantic Memory", + "The distinction between memory of specific events and interactions (episodic) and memory of general facts, preferences, and learned knowledge (semantic) - and why AI memory systems must handle both.", + "ai_engineering", + "university", + "Episodic memory is your diary ('on Tuesday, the user asked about Python async'). Semantic memory is your knowledge base ('the user prefers Python over JavaScript'). One is timestamped events, the other is distilled truths.", + None, + "Most memory systems only do episodic (conversation history) or only semantic (extracted facts). Production systems need both: episodic for recency and context ('you mentioned this yesterday'), semantic for personalization ('you prefer concise answers'). Letta/MemGPT's tiered model (core/archival/recall) maps directly to this split. Semantic memories should consolidate over time - three mentions of Python preference become one high-confidence entry, not three duplicates.", + ), + ( + "entity-memory", + "Entity Memory", + "Tracking and maintaining structured profiles of named entities (people, projects, organizations) mentioned across conversations, with attributes that update as new information arrives.", + "ai_engineering", + "university", + "A CRM that builds itself from conversations - every time someone mentions 'Alex from engineering', the system updates Alex's profile with any new information: role, team, preferences, recent projects.", + None, + "Entity memory bridges episodic and semantic: it groups facts around real-world entities. Zep's entity extraction builds entity graphs automatically. Key challenges: coreference resolution ('she' = 'the CEO' = 'Maria'), conflicting updates (old info vs new), and entity merging (detecting that 'Alex' in two conversations is the same person). Store entities with a last-updated timestamp and source provenance so stale attributes can be identified.", + ), + ( + "memory-write-policy", + "Memory Write Policy", + "The rules governing when, what, and how memory entries are created, updated, or forgotten - including deduplication, conflict resolution, capacity limits, and user control over what the system remembers.", + "ai_engineering", + "university", + "The editorial policy of a newspaper deciding what's newsworthy enough to print, what contradicts yesterday's story and needs a correction, and what gets archived when the paper runs out of space.", + None, + "Write-everything is the naive default and it fails fast - memory fills with noise, retrieval degrades, and users lose trust. A good write policy answers: Is this new information or a duplicate? Does it contradict existing memory (conflict resolution)? Is it important enough to store (salience filtering)? When should it expire? OpenAI's ChatGPT memory lets users delete entries - user control is not optional, it's a trust requirement. Mem0 implements dedup via embedding similarity before writes.", + ), + ( + "retrieval-memory", + "Retrieval Memory", + "Fetching relevant memory entries at inference time by combining recency weighting, semantic similarity, and importance scoring to inject the right context into the LLM's prompt without exceeding the context window.", + "ai_engineering", + "university", + "A librarian who knows which books you'll need before your meeting - pulling the three most relevant ones based on what you're about to discuss, not dumping every book you've ever checked out on your desk.", + None, + "Memory retrieval is a ranking problem with three signals: recency (when was it stored?), relevance (semantic similarity to current query), and importance (how salient was it?). Letta/MemGPT pages memory in and out of the context window like virtual memory in an OS. The context budget is fixed, so retrieval must be selective - injecting 20 stale memories crowds out the ones that matter. Hybrid retrieval (vector + recency decay) consistently outperforms pure semantic search for memory.", + ), + # Module 6: Tool Use & Actions + ( + "function-calling", + "Function Calling", + "The protocol by which an LLM emits a structured tool invocation (function name + arguments) instead of plain text, delegating execution to the host application.", + "ai_engineering", + "university", + "The model writes a sticky note saying 'call this function with these inputs' and hands it to you. You run the function, write the result on another sticky note, and hand it back.", + None, + "The model never executes code - it proposes a call, you execute it. Always validate arguments before execution. Forced tool use (tool_choice) guarantees a call when you need one. Error messages should be model-readable so the LLM can self-correct on the next turn.", + ), + ( + "tool-schema-design", + "Tool Schema Design", + "Defining the JSON Schema that describes a tool's name, parameters, types, and descriptions so the LLM knows what tools are available and how to invoke them correctly.", + "ai_engineering", + "university", + "Writing the instruction manual for a power tool - if the manual is clear about what each knob does and what values are valid, anyone can use it correctly on the first try.", + None, + "Descriptions matter more than names - the model reads them to decide which tool to use. Keep parameter count low (≤5) and use enums for constrained values. A poorly described tool with correct schema still fails; a well-described tool with loose schema hallucinates arguments. Nest objects only when the domain demands it.", + ), + ( + "parallel-tool-use", + "Parallel Tool Use", + "Issuing multiple independent tool calls in a single model turn, allowing the host to execute them concurrently and return all results together.", + "ai_engineering", + "university", + "Ordering appetizers and drinks at the same time instead of waiting for one to arrive before ordering the other - the kitchen and bar work in parallel.", + None, + "The model decides independence, not you. Disable parallel tool use when calls have side effects that depend on execution order. Anthropic and OpenAI both support this natively. Batch the results into a single user-turn response - one tool_result per call, order matching the call IDs.", + ), + ( + "streaming-tool-results", + "Streaming Tool Results", + "Progressively delivering tool call arguments and execution results as server-sent events so the UI can show real-time progress instead of blocking on the full response.", + "ai_engineering", + "university", + "A live sports scoreboard updating in real time instead of showing the final score only after the game ends.", + None, + "Stream the argument tokens to detect and display the tool call before execution starts - users see what the model wants to do. After execution, stream the tool result into the next model turn. Anthropic's streaming emits input_json_delta events for partial arguments. Buffer partial JSON until it's parseable.", + ), + ( + "managed-tool-runtime", + "Managed Tool Runtime", + "A hosted execution environment where tool definitions, file storage, and sandboxed code execution are managed by the provider, abstracting away infrastructure concerns.", + "ai_engineering", + "university", + "Renting a fully equipped commercial kitchen instead of building your own - the ovens, fridges, and health inspections are someone else's problem.", + None, + "OpenAI Assistants API is the canonical example: threads manage conversation state, runs manage execution, and built-in tools (code interpreter, file search) run in sandboxed containers. Tradeoff: less control over retrieval quality and execution logic, but zero infrastructure. Self-hosted runtimes (LangGraph Cloud, Modal) sit in the middle - you own the logic, they own the infra.", + ), + # Module 7: Evals & Observability + ( + "eval-driven-development", + "Eval-Driven Development", + "A development workflow where LLM behavior changes are gated by automated evaluation suites that run before deployment, treating evals as the test suite for non-deterministic systems.", + "ai_engineering", + "university", + "Test-driven development for AI - instead of unit tests that check exact outputs, you run a scoring suite that checks whether the model's answers are still good enough after your change.", + None, + "Every prompt change, model swap, or pipeline tweak needs a before/after eval run. Golden datasets (curated input-output pairs with human judgments) are the ground truth. CI gates on eval score regressions prevent silent quality degradation. Start with 20-50 golden cases covering happy paths, edge cases, and known failure modes.", + ), + ( + "rag-metrics", + "RAG Metrics", + "Specialized evaluation metrics for retrieval-augmented generation systems that separately measure retrieval quality (context precision, recall) and generation quality (faithfulness, answer relevancy).", + "ai_engineering", + "university", + "Grading a research paper on two axes: did the student find the right sources (retrieval), and did they accurately represent what those sources say (generation)?", + None, + "RAGAS defines four core metrics: faithfulness (is the answer supported by context?), answer relevancy (does it address the question?), context precision (are retrieved chunks relevant?), and context recall (did retrieval find all needed evidence?). Faithfulness and context recall are the most stable for CI gating. Response relevancy has high run-to-run variance - don't gate on it alone.", + ), + ( + "llm-tracing", + "LLM Tracing", + "Capturing the full execution trace of an LLM application - every prompt, completion, tool call, retrieval step, and latency measurement - as structured spans in a distributed trace.", + "ai_engineering", + "university", + "A flight recorder for your AI system - every decision, API call, and intermediate result is logged in sequence so you can replay exactly what happened when something goes wrong.", + None, + "Traces are the only way to debug multi-step agent failures. Each span captures: input, output, latency, token count, and model version. LangSmith, Braintrust, and Arize Phoenix all use OpenTelemetry-compatible span formats. Log trace IDs in your API responses so you can jump from a user complaint to the exact execution path. Sampling rates matter in production - trace 100% in staging, 1-10% in prod.", + ), + ( + "embedding-drift", + "Embedding Drift", + "The phenomenon where embedding quality degrades over time as the data distribution shifts, new vocabulary appears, or the embedding model is updated, causing retrieval accuracy to silently decay.", + "ai_engineering", + "university", + "A map drawn last year that doesn't show the new roads - the territory changed but your navigation system still uses the old map, so it gives wrong directions without telling you.", + None, + "Drift has three causes: data distribution shift (new topics your model wasn't trained on), model updates (re-embedding with a new model version changes the vector space), and index staleness (old documents never re-embedded). Monitor by tracking retrieval recall on a fixed golden set over time. When recall drops, re-embed. Arize Phoenix visualizes embedding clusters to spot drift visually. Never mix embeddings from different model versions in the same index.", + ), + ( + "scoring-pipeline", + "Scoring Pipeline", + "An automated pipeline that takes LLM outputs, runs them through a battery of evaluators (LLM-as-judge, heuristic checks, reference comparison), and produces structured quality scores for each dimension.", + "ai_engineering", + "university", + "An assembly line quality check - every product coming off the line gets inspected by multiple specialized checkers (grammar, accuracy, safety) before it ships.", + None, + "Three evaluator types: heuristic (regex, length, format checks - fast and deterministic), reference-based (BLEU, ROUGE, exact match against gold answers), and LLM-as-judge (a stronger model scores the output on rubric dimensions). LLM-as-judge is the most flexible but needs calibration against human scores. Always run multiple evaluators and weight their scores. Store all scores with the trace so you can analyze failure patterns over time.", + ), + # Module 8: Production Patterns + ( + "gpu-optimized-serving", + "GPU-Optimized Serving", + "Inference-time optimizations that maximize throughput and minimize latency on GPU hardware, including quantization, KV-cache management, continuous batching, and tensor parallelism.", + "ai_engineering", + "university", + "Tuning a race car engine - same car, same fuel, but adjusting compression ratio, air intake, and fuel injection to extract maximum speed from the hardware you already have.", + None, + "Three levers: quantization (FP8/INT4 reduces memory, fits larger batches), continuous batching (new requests join mid-batch instead of waiting), and tensor parallelism (split layers across GPUs). TensorRT-LLM and vLLM handle these differently - TensorRT compiles static graphs for peak throughput, vLLM uses PagedAttention for dynamic workloads. Measure tokens/second/dollar, not just tokens/second.", + ), + ( + "constrained-generation", + "Constrained Generation", + "Restricting the token-level output space of an LLM at decode time so it can only produce syntactically valid outputs matching a grammar, regex, or schema.", + "ai_engineering", + "university", + "Bowling with bumpers - the ball can only go down the lane, never into the gutter. The model generates freely within bounds that guarantee valid structure.", + None, + "Works at the logit level: mask out tokens that would violate the grammar before sampling. SGLang and Outlines implement this efficiently using finite-state machines that track valid next tokens. Constrained generation guarantees syntactic validity (valid JSON) but not semantic correctness (right values). Combine with schema validation for both.", + ), + ( + "structured-output-enforcement", + "Structured Output Enforcement", + "Ensuring LLM outputs conform to a typed schema (JSON Schema, Pydantic model, or TypeScript type) through a combination of constrained decoding, response parsing, and retry logic.", + "ai_engineering", + "university", + "A form with required fields and dropdown menus - the user can fill in the content freely, but the structure is enforced by the form itself.", + None, + "Three enforcement layers: constrained decoding (grammar-level guarantee), response parsing with validation (Pydantic, Zod), and retry-on-failure with the parse error fed back to the model. Anthropic and OpenAI both offer native JSON mode, but schema-strict mode (OpenAI) goes further by guaranteeing exact key/type compliance. For production, always validate after generation even when using constrained decoding - belt and suspenders.", + ), + ( + "io-validation", + "I/O Validation", + "Systematic checking of both inputs to and outputs from an LLM for prompt injection, PII leakage, off-topic content, schema violations, and hallucination indicators before passing results downstream.", + "ai_engineering", + "university", + "Airport security for your AI pipeline - every input gets scanned on the way in (prompt injection, PII), every output gets scanned on the way out (hallucination, toxicity, schema violations).", + None, + "Input validators: prompt injection detection (classifier or heuristic), PII redaction (regex + NER), topic guardrails (embedding similarity to allowed domains). Output validators: schema compliance (JSON Schema), hallucination checks (NLI against retrieved context), toxicity scoring, and PII re-scan. Guardrails AI composes these as a validator chain. Fail-open vs fail-closed is the critical design decision - production systems should fail closed (reject) by default.", + ), + ( + "safety-layer", + "Safety Layer", + "A defense-in-depth architecture that wraps LLM calls with input filtering, output validation, rate limiting, and content policy enforcement as composable middleware.", + "ai_engineering", + "university", + "The layers of security in a bank - locked front door, ID check, vault combination, and cameras. No single layer is foolproof, but stacking them makes a breach exponentially harder.", + None, + "A safety layer is not one check but an ordered pipeline: rate limiting (prevent abuse), input screening (block injection/PII), model call, output validation (catch hallucination/toxicity), and audit logging. Each layer has a policy: block, warn, or pass-with-flag. Guardrails AI, LLM Guard, and NVIDIA NeMo Guardrails all implement this pattern differently. The key insight: safety checks add latency, so run cheap heuristic filters first and expensive LLM-based classifiers only when heuristics are ambiguous.", + ), + ( + "retrieval-grounding", + "Retrieval Grounding", + "Conditioning LLM generation on retrieved evidence so answers cite real sources instead of hallucinating, with mechanisms to detect when retrieval quality is insufficient.", + "ai_engineering", + "university", + "An open-book exam - the model writes answers using the passages you hand it, and you can check its work against those passages.", + None, + "An answerability gate (minimum rerank score) prevents generation from thin evidence. Faithfulness metrics measure whether the answer is supported by retrieved chunks. The retrieval pipeline's quality ceiling is the generation quality ceiling.", + ), +] + + +def seed(dry_run: bool = False) -> bool: + """Seed concept rows + dossiers for AI Engineering track.""" + from backend.services.chat.history import get_db, init_db + + init_db() + conn = get_db() + + for cid, name, desc, lens, level, intuition, formula, insight in CONCEPTS: + conn.execute( + """INSERT OR REPLACE INTO concepts + (id, name, description, lens, level, source_count) + VALUES (?, ?, ?, ?, ?, 0)""", + (cid, name, desc, lens, level), + ) + + formulas = ( + [{"content": formula, "content_latex": formula, + "source_id": SRC, "source_title": None}] + if formula else [] + ) + insights = [{"content": insight, "source_id": SRC}] + atom_count = 2 + (1 if formula else 0) + 1 + + conn.execute( + """INSERT OR REPLACE INTO concept_dossiers + (concept_id, formal_definition, intuitive_definition, + formulas_json, examples_json, prerequisite_claims_json, + key_insights_json, source_ids_json, + atom_count, source_count, built_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, datetime('now'))""", + ( + cid, desc, intuition, + json.dumps(formulas), "[]", "[]", + json.dumps(insights), json.dumps([SRC]), + atom_count, + ), + ) + + if dry_run: + logger.info("Dry run - %d concepts validated, rolling back", len(CONCEPTS)) + conn.rollback() + else: + conn.commit() + logger.info("Seeded %d concepts + dossiers", len(CONCEPTS)) + + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Bootstrap AI Engineering concepts") + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + success = seed(dry_run=args.dry_run) + sys.exit(0 if success else 1) diff --git a/backend/jobs/seed_backend_systems_concepts.py b/backend/jobs/seed_backend_systems_concepts.py new file mode 100644 index 00000000..7813abe8 --- /dev/null +++ b/backend/jobs/seed_backend_systems_concepts.py @@ -0,0 +1,395 @@ +"""Bootstrap Backend & Distributed Systems concepts and dossiers. + +Inserts concept rows + dossiers for all six Backend Systems modules +so the curriculum linker resolves titles and readiness correctly. + +Usage: + python -m backend.jobs.seed_backend_systems_concepts + python -m backend.jobs.seed_backend_systems_concepts --dry-run +""" + +import argparse +import json +import logging +import sys + +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") +logger = logging.getLogger(__name__) + +SRC = "curriculum:backend-systems-v1" + +# (id, name, description, lens, level, intuition, formula, insight) +CONCEPTS = [ + # Module 1: Real-Time Systems + ( + "realtime-vs-request", + "Real-Time vs Request/Response", + "When a system needs continuous data flow instead of discrete ask-and-answer exchanges.", + "systems", + "university", + "Request/response is like sending letters back and forth. Real-time is like a phone call - the line stays open.", + None, + "Chat apps, live dashboards, collaborative editing, multiplayer games.", + ), + ( + "push-vs-poll", + "Push vs Poll", + "Two strategies for delivering updates: the server pushes when data changes, or the client polls on a timer.", + "systems", + "university", + "Push is a doorbell - you know immediately when someone arrives. Poll is checking the window every 30 seconds.", + None, + "WebSocket push for live UX, long-polling as fallback, SSE for one-way streams.", + ), + ( + "pub-sub", + "Publish-Subscribe", + "Messaging pattern where publishers emit events to topics and subscribers receive them without direct coupling.", + "systems", + "university", + "A radio station broadcasts to anyone tuned in - the station doesn't need to know who's listening.", + None, + "Notification fan-out, event-driven microservices, real-time feeds, chat rooms.", + ), + ( + "backpressure", + "Backpressure", + "Flow control mechanism where a slow consumer signals the producer to reduce sending rate.", + "systems", + "university", + "A conveyor belt that slows down when boxes pile up at the end instead of letting them fall off.", + None, + "Stream processing (Kafka consumer lag), WebSocket flow control, TCP window sizing.", + ), + ( + "stateful-connections", + "Stateful Connections", + "Persistent connections that maintain session context between client and server across multiple exchanges.", + "systems", + "university", + "Stateless is showing your ID every time you enter. Stateful is getting a wristband at the door.", + None, + "WebSocket sessions, gRPC streams, multiplayer game servers, Durable Objects.", + ), + # Module 2: APIs at Scale + ( + "rest-vs-rpc", + "REST vs RPC", + "Two dominant styles for service interfaces: resource-oriented REST with HTTP verbs, or action-oriented RPC with typed contracts.", + "systems", + "university", + "REST is a menu where you pick items by name. RPC is calling the kitchen directly and telling them exactly what to cook.", + None, + "Public APIs favor REST for discoverability. Internal services favor gRPC for performance and type safety.", + ), + ( + "api-gateway", + "API Gateway", + "A reverse proxy that sits between clients and backend services, handling auth, rate limiting, routing, and observability.", + "systems", + "university", + "A hotel concierge - guests talk to one person who routes requests to the right department and enforces house rules.", + None, + "Kong, AWS API Gateway, Envoy. Centralizes cross-cutting concerns so services stay focused on business logic.", + ), + ( + "internal-vs-external-api", + "Internal vs External APIs", + "The distinction between APIs consumed by your own services and those exposed to third-party developers with different trust and stability guarantees.", + "systems", + "university", + "Internal APIs are hallway conversations - fast, informal, easy to change. External APIs are published contracts - slow to change, well-documented.", + None, + "Internal: gRPC, loose versioning, fast iteration. External: REST, semantic versioning, deprecation policies, developer portals.", + ), + ( + "protocol-selection", + "Protocol Selection", + "Choosing the right communication protocol (HTTP/1.1, HTTP/2, HTTP/3, WebSocket, gRPC) based on latency, throughput, and client constraints.", + "systems", + "university", + "Choosing a shipping method - overnight express for urgent packages, freight for bulk, same-day courier for local.", + None, + "HTTP/2 multiplexing for browser APIs, gRPC over HTTP/2 for service mesh, WebSocket for bidirectional streams.", + ), + ( + "api-design-patterns", + "API Design Patterns", + "Recurring solutions for common API challenges: pagination, filtering, versioning, error formats, and idempotency keys.", + "systems", + "university", + "Building blocks like LEGO - standard pieces (pagination, error codes, versioning) that snap together into any API shape.", + None, + "Cursor-based pagination for feeds, idempotency keys for payments, structured error envelopes, field masks for partial updates.", + ), + # Module 3: Distributed Systems Fundamentals + ( + "replication", + "Replication", + "Keeping copies of the same data on multiple machines for fault tolerance, latency reduction, or read throughput.", + "systems", + "university", + "Replication is photocopying a document and storing copies in different offices - if one office burns down, the others still have it.", + None, + "Leader-follower (Postgres streaming), multi-leader (CRDTs in collaborative editing), leaderless (Dynamo-style quorum reads/writes).", + ), + ( + "partitioning", + "Partitioning", + "Splitting a dataset across multiple nodes so each node owns a subset, enabling horizontal scaling beyond a single machine's capacity.", + "systems", + "university", + "A library that splits its collection across branches by genre - each branch is smaller and faster to search, but you need to know which branch has your book.", + None, + "Hash partitioning (even distribution, no range queries), range partitioning (sorted scans, hotspot risk), consistent hashing (minimal reshuffling on node changes).", + ), + ( + "consistency-models", + "Consistency Models", + "The contract a distributed system offers about what values a read can return after a write - from strict linearizability to eventual consistency.", + "systems", + "university", + "Strong consistency is a shared Google Doc where everyone sees the same thing instantly. Eventual consistency is email - everyone gets the update, but not at the same time.", + None, + "Linearizability (strongest, highest latency), causal consistency (respects happens-before), eventual consistency (DynamoDB, Cassandra default). Choose based on correctness vs availability needs.", + ), + ( + "consensus", + "Consensus", + "The problem of getting multiple nodes to agree on a single value or sequence of operations, even when some nodes fail.", + "systems", + "university", + "A group of friends trying to pick a restaurant over text - someone proposes, others vote, and once a majority agrees, the decision is final even if someone's phone dies.", + None, + "Raft (etcd, CockroachDB), Paxos (Chubby, Spanner), ZAB (ZooKeeper). All solve the same core problem with different tradeoffs on understandability and performance.", + ), + ( + "failure-modes", + "Failure Modes", + "The ways distributed systems break: network partitions, node crashes, Byzantine faults, partial failures, and cascading failures.", + "systems", + "university", + "A highway system - a single lane closure (partial failure) can cause backups everywhere (cascading failure), and you can't always tell if the road ahead is clear (network partition).", + None, + "Crash-stop vs crash-recovery vs Byzantine. Network partitions are guaranteed (see Jepsen). Design for partial failure - the system must keep working when some parts don't.", + ), + # Module 4: Event-Driven Architecture + ( + "event-driven-vs-request", + "Event-Driven vs Request/Response", + "Two communication paradigms: request/response where callers wait for answers, and event-driven where producers emit facts and consumers react independently.", + "systems", + "university", + "Request/response is asking someone a question and waiting for the answer. Event-driven is posting on a bulletin board - anyone interested reads it on their own time.", + None, + "Order service emits OrderPlaced; inventory, billing, and shipping each react independently. No service waits on another. Contrast with synchronous REST chains that cascade failures.", + ), + ( + "queue-vs-log", + "Queue vs Log", + "Two message storage models: queues delete messages after delivery (work distribution), logs retain messages for replay (event history).", + "systems", + "university", + "A queue is a to-do list - once you check off an item, it's gone. A log is a diary - everything stays, and anyone can read back through it.", + None, + "RabbitMQ (queue): message deleted after ack, fan-out via exchanges. Kafka (log): messages retained by time/size, consumers track their own offset. Choose queue for task distribution, log for event sourcing and replay.", + ), + ( + "event-replay", + "Event Replay", + "The ability to re-process historical events from a durable log to rebuild state, backfill new consumers, or recover from bugs.", + "systems", + "university", + "Replay is rewinding a recording to watch it again. If you add a new TV to the house, you can catch it up on everything it missed.", + None, + "Kafka consumer offset reset for backfill. Rebuilding a read model from an event store. Replaying production events in staging to reproduce bugs. Requires idempotent consumers.", + ), + ( + "idempotency", + "Idempotency", + "The property that performing an operation multiple times produces the same result as performing it once - critical for safe retries in distributed systems.", + "systems", + "university", + "Pressing an elevator button twice doesn't call two elevators. The second press has no effect because the request is already registered.", + None, + "Idempotency keys for payment APIs (Stripe). Deduplication in Kafka consumers (exactly-once semantics). PUT vs POST in REST. At-least-once delivery is easy; idempotency makes it safe.", + ), + ( + "eventual-consistency", + "Eventual Consistency", + "A consistency model where all replicas converge to the same state given enough time, but reads may temporarily return stale data.", + "systems", + "university", + "Updating your profile picture - your friend in another country might see the old one for a few seconds, but eventually everyone sees the new one.", + None, + "DynamoDB, Cassandra, DNS propagation. Acceptable for social feeds, product catalogs, analytics. Not acceptable for bank balances or inventory counts that must be exact. Often paired with conflict resolution (last-writer-wins, CRDTs).", + ), + ( + "event-sourcing", + "Event Sourcing", + "Persisting all changes as an append-only sequence of domain events instead of overwriting current state, enabling full audit trails and temporal queries.", + "systems", + "university", + "Instead of updating your bank balance directly, the bank records every deposit and withdrawal. Your balance is calculated by replaying all transactions from the beginning.", + None, + "EventStoreDB, Axon Framework. Full audit trail, time-travel queries, easy debugging. Tradeoffs: storage growth, query complexity (needs read projections/CQRS), eventual consistency between write and read models.", + ), + # Module 5: Containers & Orchestration + ( + "containerization", + "Containerization", + "Packaging an application with its dependencies into an isolated, portable unit that runs consistently across any host.", + "systems", + "university", + "A shipping container - the crane doesn't care what's inside, it just needs the standard shape. Your code, runtime, and libraries travel as one sealed box.", + None, + "Docker images, OCI spec, multi-stage builds for small images, layer caching for fast CI. Containers solve 'works on my machine' by making the machine part of the artifact.", + ), + ( + "orchestration", + "Orchestration", + "Automated management of container lifecycles across a cluster: deploying, restarting, scaling, and networking containers without manual intervention.", + "systems", + "university", + "An orchestra conductor - each musician (container) plays their part, but the conductor decides who plays when, handles tempo changes, and brings in substitutes if someone drops out.", + None, + "Kubernetes Deployments, rollout strategies (rolling, blue-green, canary), health checks (liveness/readiness probes), and declarative desired-state reconciliation.", + ), + ( + "scheduling", + "Scheduling", + "The process of assigning containers to specific nodes in a cluster based on resource requirements, constraints, and optimization goals.", + "systems", + "university", + "An airport gate agent assigning passengers to seats - match requirements (window seat, extra legroom) to available capacity while keeping the plane balanced.", + None, + "Kubernetes scheduler: resource requests/limits, node affinity, taints/tolerations, pod anti-affinity for HA. Bin-packing vs spreading tradeoff affects cost and resilience.", + ), + ( + "service-discovery", + "Service Discovery", + "The mechanism by which services locate each other in a dynamic environment where instances are created, moved, and destroyed continuously.", + "systems", + "university", + "DNS for your apartment building - instead of memorizing which unit everyone lives in, you look up their name and get the current address, even after they move.", + None, + "Kubernetes DNS (ClusterIP services), Consul, etcd. Client-side vs server-side discovery. Without it, every deployment becomes a config change in every caller.", + ), + ( + "scaling-primitives", + "Scaling Primitives", + "The foundational mechanisms for adjusting compute capacity to match demand: horizontal pod autoscaling, vertical scaling, and cluster-level node scaling.", + "systems", + "university", + "Horizontal scaling is adding more checkout lanes when the store gets busy. Vertical scaling is replacing a cashier with a faster one. Node scaling is building more stores.", + None, + "HPA (CPU/memory/custom metrics), VPA (right-sizing requests), Cluster Autoscaler / Karpenter (node provisioning). Scale on the metric closest to user impact, not just CPU.", + ), + # Module 6: Production Hardening + ( + "observability", + "Observability", + "The ability to understand a system's internal state from its external outputs - logs, metrics, and traces - without deploying new code.", + "systems", + "university", + "A car dashboard - you don't open the engine to check speed, temperature, and fuel. Instruments on the outside tell you what's happening inside.", + None, + "Three pillars: metrics (Prometheus counters/gauges/histograms), logs (structured JSON, correlation IDs), traces (OpenTelemetry spans across service boundaries). Instrument at service boundaries first.", + ), + ( + "load-testing", + "Load Testing", + "Systematically applying synthetic traffic to a system to measure throughput, latency, and failure thresholds before real users hit them.", + "systems", + "university", + "A fire drill - you simulate the emergency before it happens so you know where the exits jam and how fast people can get out.", + None, + "k6, Locust, Gatling. Test types: baseline (normal traffic), stress (find the breaking point), soak (memory leaks over hours). Always define pass/fail thresholds - a load test without SLOs is just a demo.", + ), + ( + "slo-error-budget", + "SLOs and Error Budgets", + "A framework for defining reliability targets (SLOs) and using the gap between target and perfection (error budget) to balance feature velocity with operational stability.", + "systems", + "university", + "A monthly spending budget - your SLO is how much reliability you promise, the error budget is how much you're allowed to break before you stop shipping and fix things.", + None, + "SLI (what you measure: p99 latency, error rate), SLO (the target: 99.9% success), error budget (the slack: 0.1% failures allowed). When budget is spent, freeze deploys and fix reliability. Google SRE Book ch. 4.", + ), + ( + "incident-response", + "Incident Response", + "The structured process for detecting, triaging, mitigating, and learning from production failures - from alert to postmortem.", + "systems", + "university", + "A hospital emergency room - triage (severity), stabilize (stop the bleeding), diagnose (root cause), recover, then debrief so the same injury doesn't happen again.", + None, + "Roles: incident commander, communications lead, subject-matter responders. Runbooks for known failure modes. Blameless postmortems that fix systems, not blame people. PagerDuty, Opsgenie for on-call routing.", + ), + ( + "deployment-strategy", + "Deployment Strategies", + "Patterns for releasing new code to production while minimizing blast radius - rolling updates, blue-green, canary, and feature flags.", + "systems", + "university", + "Remodeling a restaurant - rolling update replaces one table at a time, blue-green builds a second dining room and switches guests over, canary seats one table in the new room first to test.", + None, + "Rolling (K8s default, gradual pod replacement), blue-green (instant cutover, double resources), canary (traffic splitting by percentage), feature flags (deploy dark, activate later). Choose based on rollback speed vs resource cost.", + ), +] + + +def seed(dry_run: bool = False) -> bool: + """Seed 31 concept rows + dossiers for Backend Systems track.""" + from backend.services.chat.history import get_db, init_db + + init_db() + conn = get_db() + + for cid, name, desc, lens, level, intuition, formula, insight in CONCEPTS: + conn.execute( + """INSERT OR REPLACE INTO concepts + (id, name, description, lens, level, source_count) + VALUES (?, ?, ?, ?, ?, 0)""", + (cid, name, desc, lens, level), + ) + + formulas = ( + [{"content": formula, "content_latex": formula, + "source_id": SRC, "source_title": None}] + if formula else [] + ) + insights = [{"content": insight, "source_id": SRC}] + atom_count = 2 + (1 if formula else 0) + 1 # def + intuition + formula? + insight + + conn.execute( + """INSERT OR REPLACE INTO concept_dossiers + (concept_id, formal_definition, intuitive_definition, + formulas_json, examples_json, prerequisite_claims_json, + key_insights_json, source_ids_json, + atom_count, source_count, built_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, datetime('now'))""", + ( + cid, desc, intuition, + json.dumps(formulas), "[]", "[]", + json.dumps(insights), json.dumps([SRC]), + atom_count, + ), + ) + + if dry_run: + logger.info("Dry run - %d concepts validated, rolling back", len(CONCEPTS)) + conn.rollback() + else: + conn.commit() + logger.info("Seeded %d concepts + dossiers", len(CONCEPTS)) + + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Bootstrap Backend Systems concepts") + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + success = seed(dry_run=args.dry_run) + sys.exit(0 if success else 1) diff --git a/backend/jobs/seed_mlops_concepts.py b/backend/jobs/seed_mlops_concepts.py index 3c1067a2..5936ae64 100644 --- a/backend/jobs/seed_mlops_concepts.py +++ b/backend/jobs/seed_mlops_concepts.py @@ -1,6 +1,6 @@ """Bootstrap Track 3 (MLOps & Production ML) concepts and dossiers. -Inserts 24 concept rows + real dossiers (definition, intuition, formula, +Inserts 26 concept rows + real dossiers (definition, intuition, formula, ml_usage) so the explainer and practice generator have usable content. Usage: @@ -53,11 +53,15 @@ ("model-rollback", "Model Rollback", "Quickly reverting to a previous model version when the new one degrades.", "machine_learning", "university", "Hit undo on a bad deployment.", None, "Blue-green deploys, model registry version pinning."), ("model-governance", "Model Governance", "Policies and processes for approving, auditing, and documenting models in production.", "machine_learning", "university", "Who approved this model, what data did it train on, and is it still compliant?", None, "Model cards, audit logs, EU AI Act compliance."), ("technical-debt-ml", "Technical Debt in ML", "Hidden costs from shortcuts in ML systems that compound over time.", "machine_learning", "university", "Every quick fix in an ML system makes the next change harder.", None, "Glue code, pipeline jungles, undeclared dependencies."), + + # Job profile coverage + ("ml-ops", "MLOps", "The discipline of deploying, monitoring, and maintaining machine learning models in production through automation, CI/CD, and operational best practices.", "ai_engineering", "university", "Software engineering already solved how to ship code reliably with DevOps. MLOps applies the same ideas - version control, testing, monitoring, rollback - to the messier world of models, data, and training pipelines.", None, "The hardest part of MLOps is not serving a model - it is detecting when a model that appears healthy is silently degrading because the world changed underneath it."), + ("continual-learning", "Continual Learning", "Training a model incrementally on new data without forgetting previously learned knowledge, avoiding the need for full retraining from scratch.", "machine_learning", "university", "Imagine studying math for a year, then switching to history - and forgetting all the math. Neural networks do exactly this when trained on new data naively. Continual learning is the set of techniques that prevent this catastrophic forgetting.", None, "In production systems, continual learning is less about exotic algorithms and more about deciding when to retrain, how much old data to replay, and how to validate that new knowledge did not corrupt existing capabilities."), ] def seed(dry_run: bool = False) -> bool: - """Seed 24 concept rows + real dossiers from Track 3 artifact.""" + """Seed 26 concept rows + real dossiers from Track 3 artifact.""" from backend.services.chat.history import get_db, init_db init_db() diff --git a/backend/main.py b/backend/main.py index 860ccbc8..55805c9a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -49,6 +49,7 @@ def _safe_logfire_instrumentation(name: str, instrumenter: Callable[[], None]) - from starlette.middleware.base import BaseHTTPMiddleware from backend.api import ( + alignment, apps, brand_studio, cantonese, @@ -75,9 +76,11 @@ def _safe_logfire_instrumentation(name: str, instrumenter: Callable[[], None]) - oss_projects, personal, profile, + profiles, projects, reference_tracks, search, + signals, social, sonic, source_assets, @@ -379,6 +382,9 @@ async def career_recommendation_http_error_handler(request: Request, exc: HTTPEx app.include_router(youtube.router, tags=["youtube"]) app.include_router(opportunities.router, tags=["opportunities"]) app.include_router(search.router, prefix="/api", tags=["search"]) +app.include_router(signals.router, tags=["signals"]) +app.include_router(profiles.router, tags=["profiles"]) +app.include_router(alignment.router, tags=["alignment"]) app.include_router(source_assets.router, prefix="/api", tags=["source-assets"]) diff --git a/backend/models/ingest.py b/backend/models/ingest.py index c86c290b..74d5e840 100644 --- a/backend/models/ingest.py +++ b/backend/models/ingest.py @@ -17,6 +17,7 @@ class ContentType(str, Enum): PDF = "pdf" MARKDOWN = "markdown" TEXT = "text" + JOB = "job" class IngestStatus(str, Enum): diff --git a/backend/models/library.py b/backend/models/library.py index fdb435c4..1452c970 100644 --- a/backend/models/library.py +++ b/backend/models/library.py @@ -7,8 +7,8 @@ from backend.models.learnings import Learnings -SourceType = Literal["youtube", "document", "article"] -SourceFilterType = Literal["youtube", "document", "article", "obsidian"] +SourceType = Literal["youtube", "document", "article", "job"] +SourceFilterType = Literal["youtube", "document", "article", "job", "obsidian"] class SourceItem(BaseModel): diff --git a/backend/services/alignment_gate.py b/backend/services/alignment_gate.py new file mode 100644 index 00000000..2f2a2b19 --- /dev/null +++ b/backend/services/alignment_gate.py @@ -0,0 +1,151 @@ +"""Deterministic alignment gate for proposed concepts. + +Checks whether a concept aligns with job profiles and curriculum. +No LLM, no storage, no mutations — pure filter. +""" + +from __future__ import annotations + +import logging + +from backend.services.chat.history import get_db +from backend.services.job_profiles import load_job_profiles + +logger = logging.getLogger(__name__) + + +def evaluate(normalized_name: str, signal_type: str = "concept") -> dict: + """Evaluate whether a proposed concept aligns with profiles and curriculum. + + Check order: + 1. Exact match on concepts.id → already_covered + 2. Alias match on concept_aliases.alias → already_covered + 3. No match in any job profile bucket → reject + 4. Profile match but frequency < 2 → pending + 5. Profile match with sufficient evidence → candidate + """ + conn = get_db() + name_lower = normalized_name.lower().strip() + + # --- 1. Exact concept match --- + concept_row = conn.execute( + "SELECT id, name FROM concepts WHERE id = ?", (name_lower,) + ).fetchone() + + if concept_row: + related = _find_related_concepts(conn, concept_row["id"]) + return _result( + decision="already_covered", + reason=f"Concept '{concept_row['id']}' already exists in the curriculum", + proposed_concept=normalized_name, + curriculum_coverage={"already_exists": True, "related_concepts": related}, + confidence=1.0, + ) + + # --- 2. Alias match --- + alias_row = conn.execute( + "SELECT concept_id FROM concept_aliases WHERE alias = ?", (name_lower,) + ).fetchone() + + if alias_row: + related = _find_related_concepts(conn, alias_row["concept_id"]) + return _result( + decision="already_covered", + reason=f"'{normalized_name}' is an alias for concept '{alias_row['concept_id']}'", + proposed_concept=normalized_name, + curriculum_coverage={"already_exists": True, "related_concepts": related}, + confidence=1.0, + ) + + # --- 3. Job profile match (reject unrelated concepts immediately) --- + match = _match_job_profiles(name_lower) + + if not match["matched_profiles"]: + return _result( + decision="reject", + reason="No job profile references this concept", + proposed_concept=normalized_name, + curriculum_coverage={"already_exists": False, "related_concepts": []}, + confidence=0.8, + ) + + # --- 4. Signal frequency check (relevant but under-evidenced → pending) --- + freq_row = conn.execute( + "SELECT COUNT(*) AS cnt FROM signals WHERE normalized_value = ? AND signal_type = ?", + (name_lower, signal_type), + ).fetchone() + frequency = freq_row["cnt"] if freq_row else 0 + + if frequency < 2: + return _result( + decision="pending", + reason=f"Matches profile(s) but signal frequency too low ({frequency}). Waiting for more evidence.", + proposed_concept=normalized_name, + target_profile_match=match, + curriculum_coverage={"already_exists": False, "related_concepts": []}, + confidence=0.4, + ) + + # --- 5. Candidate --- + return _result( + decision="candidate", + reason=f"Matches {len(match['matched_profiles'])} profile(s): {', '.join(match['matched_profiles'])}", + proposed_concept=normalized_name, + target_profile_match=match, + curriculum_coverage={"already_exists": False, "related_concepts": []}, + gap_detected=True, + confidence=0.7, + ) + + +def _match_job_profiles(name_lower: str) -> dict: + """Check if name appears in any job profile bucket.""" + profiles = load_job_profiles() + matched_profiles: list[str] = [] + matched_buckets: list[str] = [] + + for profile_name, profile in profiles.items(): + for bucket in ("core_concepts", "infrastructure", "tools", "signals_expected"): + values = [v.lower() for v in profile.get(bucket, [])] + if name_lower in values: + if profile_name not in matched_profiles: + matched_profiles.append(profile_name) + matched_buckets.append(f"{profile_name}.{bucket}") + + return {"matched_profiles": matched_profiles, "matched_buckets": matched_buckets} + + +def _find_related_concepts(conn, concept_id: str) -> list[str]: + """Find concepts related via prerequisites (both directions).""" + rows = conn.execute( + """ + SELECT concept_id AS related FROM concept_prerequisites WHERE prerequisite_id = ? + UNION + SELECT prerequisite_id AS related FROM concept_prerequisites WHERE concept_id = ? + """, + (concept_id, concept_id), + ).fetchall() + return [r["related"] for r in rows] + + +def _result( + *, + decision: str, + reason: str, + proposed_concept: str, + curriculum_coverage: dict, + confidence: float, + target_profile_match: dict | None = None, + gap_detected: bool = False, + proposed_track: str | None = None, +) -> dict: + return { + "decision": decision, + "reason": reason, + "proposed_concept": proposed_concept, + "target_profile_match": target_profile_match, + "curriculum_coverage": curriculum_coverage, + "gap_detected": gap_detected, + "proposed_track": proposed_track, + "confidence": confidence, + } diff --git a/backend/services/career/__init__.py b/backend/services/career/__init__.py index dbdd4c40..e137c87e 100644 --- a/backend/services/career/__init__.py +++ b/backend/services/career/__init__.py @@ -14,7 +14,6 @@ get_composites_overview, ) from backend.services.career.inventory import get_career_taxonomy_inventory -from backend.services.career.patterns import get_patterns_overview from backend.services.career.recommendations import ( get_job_recommendation_feedback_stats, get_job_recommendations, @@ -33,7 +32,6 @@ "get_career_taxonomy_inventory", "get_career_taxonomy_overview", "get_composites_overview", - "get_patterns_overview", "get_job_recommendations", "get_job_recommendation_feedback_stats", "record_job_recommendation_feedback", diff --git a/backend/services/career/coaches.py b/backend/services/career/coaches.py index 57e60358..0cc5effe 100644 --- a/backend/services/career/coaches.py +++ b/backend/services/career/coaches.py @@ -70,7 +70,6 @@ async def get_coach_recommendations() -> CoachRecommendationsResponse: """Generate personalized coach recommendations from career profile.""" from backend.services.career.assessment import get_career_overview from backend.services.career.goal import get_career_goal - from backend.services.career.patterns import get_patterns_overview from backend.services.llm.client import ( ainvoke_with_observability, get_chat_llm, @@ -79,7 +78,6 @@ async def get_coach_recommendations() -> CoachRecommendationsResponse: ) overview = get_career_overview() - patterns = get_patterns_overview() goal = get_career_goal() if overview.user_skill_count < 15: @@ -101,23 +99,12 @@ async def get_coach_recommendations() -> CoachRecommendationsResponse: fit_pct = target_fit.coverage_pct if target_fit else overview.best_fit_pct missing_skills = (target_fit.missing_required[:8] if target_fit else overview.recommended_next_skills[:8]) - # Sort patterns by score - sorted_patterns = sorted(patterns.patterns, key=lambda p: p.score_pct, reverse=True) - strong = sorted_patterns[:3] - weak = sorted_patterns[-3:] if len(sorted_patterns) >= 3 else sorted_patterns - - strong_text = ", ".join(f"{p.name} ({p.score_pct:.0f}%)" for p in strong) - weak_text = ", ".join( - f"{p.name} ({p.score_pct:.0f}%, missing: {', '.join(p.missing_required[:2])})" - for p in weak - ) - prompt = _COACH_PROMPT.format( target_role=target_role, target_level=target_level, fit_pct=fit_pct, - strong_patterns=strong_text or "none scored yet", - weak_patterns=weak_text or "none scored yet", + strong_patterns="(not scored)", + weak_patterns="(not scored)", missing_skills=", ".join(missing_skills) or "none", existing_skills=", ".join(overview.user_skills[:20]), ) diff --git a/backend/services/career/patterns.py b/backend/services/career/patterns.py deleted file mode 100644 index 342e1a63..00000000 --- a/backend/services/career/patterns.py +++ /dev/null @@ -1,266 +0,0 @@ -"""Enterprise patterns: score pattern fit from user skill evidence.""" - -import json -from functools import lru_cache -from pathlib import Path - -from backend.models.career import ( - PatternDelta, - PatternEvidence, - PatternFit, - PatternImpactPreview, - PatternProofProject, - PatternsOverview, -) -from backend.services.career.assessment import ( - _skill_strength, - aggregate_all_learnings, - match_user_skill_evidence, -) - -PATTERN_REQUIRED_WEIGHT = 0.75 -PATTERN_OPTIONAL_WEIGHT = 0.25 - -NEAREST_LOW = 40.0 -NEAREST_HIGH = 100.0 - - -def _assets_dir() -> Path: - return Path(__file__).resolve().parents[2] / "assets" - - -def _ref_table_exists(table_name: str) -> bool: - try: - from backend.services.chat.history import get_db - - conn = get_db() - row = conn.execute( - "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", - (table_name,), - ).fetchone() - return row is not None - except Exception: - return False - - -@lru_cache(maxsize=1) -def load_patterns() -> list[dict]: - """Read enterprise patterns from DB if available, else JSON fallback.""" - if _ref_table_exists("ref_patterns"): - try: - from backend.services.chat.history import get_db - - conn = get_db() - rows = conn.execute("SELECT data_json FROM ref_patterns").fetchall() - return [json.loads(row["data_json"]) for row in rows] - except Exception: - pass - - path = _assets_dir() / "patterns.json" - with path.open(encoding="utf-8") as f: - data = json.load(f) - return data["patterns"] - - -def compute_pattern_fit( - pattern: dict, - user_skills: set[str], - skill_evidence: dict[str, int], -) -> PatternFit: - """Score a single pattern against user skill evidence.""" - required = pattern["required_concepts"] - optional = pattern.get("optional_concepts", []) - tools = pattern.get("common_tools", []) - - required_covered = [c for c in required if c in user_skills] - required_missing = [c for c in required if c not in user_skills] - optional_covered = [c for c in optional if c in user_skills] - optional_missing = [c for c in optional if c not in user_skills] - covered_tools = [t for t in tools if t in user_skills] - - required_strength = ( - sum(_skill_strength(c, skill_evidence) for c in required) / len(required) - if required - else 1.0 - ) - optional_strength = ( - sum(_skill_strength(c, skill_evidence) for c in optional) / len(optional) - if optional - else 0.0 - ) - - score = ( - PATTERN_REQUIRED_WEIGHT * required_strength - + PATTERN_OPTIONAL_WEIGHT * optional_strength - ) * 100 - - raw_evidence = pattern.get("evidence", []) - evidence = [ - PatternEvidence( - organization=e["organization"], - outcome_signals=e.get("outcome_signals", []), - implementation_depth=e.get("implementation_depth", "unknown"), - ) - for e in raw_evidence - ] - - raw_proof = pattern["proof_project"] - proof_project = PatternProofProject( - title=raw_proof["title"], - problem_framing=raw_proof["problem_framing"], - suggested_tools=raw_proof.get("suggested_tools", []), - deliverables=raw_proof.get("deliverables", []), - complexity=raw_proof.get("complexity", "unknown"), - estimated_hours=raw_proof.get("estimated_hours", 1), - ) - - return PatternFit( - pattern_id=pattern["id"], - name=pattern["name"], - description=pattern["description"], - score_pct=round(score, 1), - required_total=len(required), - required_covered=len(required_covered), - optional_total=len(optional), - optional_covered=len(optional_covered), - covered_concepts=sorted(set(required_covered + optional_covered)), - missing_required=required_missing, - missing_optional=optional_missing, - covered_tools=covered_tools, - primary_domain=pattern.get("primary_domain", ""), - tags=pattern.get("tags", []), - evidence=evidence, - proof_project=proof_project, - ) - - -def get_patterns_overview() -> PatternsOverview: - """Score all enterprise patterns and return sorted results.""" - all_concepts, all_tools = aggregate_all_learnings() - skill_evidence = match_user_skill_evidence(all_concepts, all_tools) - user_skills = set(skill_evidence) - - patterns = load_patterns() - scored = [ - compute_pattern_fit(p, user_skills, skill_evidence) - for p in patterns - ] - scored.sort(key=lambda pf: (-pf.score_pct, pf.pattern_id)) - - nearest = [ - pf for pf in scored - if NEAREST_LOW <= pf.score_pct < NEAREST_HIGH - ] - - avg_fit = ( - sum(pf.score_pct for pf in scored) / len(scored) - if scored - else 0.0 - ) - - return PatternsOverview( - patterns=scored, - nearest=nearest, - total_count=len(scored), - avg_fit_pct=round(avg_fit, 1), - ) - - -def preview_pattern_impact( - proposed_concepts: list[str] | None = None, - proposed_tools: list[str] | None = None, - exclude_source_id: str | None = None, -) -> PatternImpactPreview: - """Diff pattern fit scores before and after proposed new skills. - - When exclude_source_id is provided, that source's learnings are removed - from the baseline and used as the "proposed" delta. This supports the - post-ingest flow where learnings are already saved but the user hasn't - confirmed them yet. - """ - from backend.services.ingest.learnings_store import ( - get_learnings, - list_all_learnings, - ) - - proposed_concepts = proposed_concepts or [] - proposed_tools = proposed_tools or [] - - if exclude_source_id: - # Build baseline WITHOUT the excluded source - baseline_concepts: list[str] = [] - baseline_tools: list[str] = [] - for sid in list_all_learnings(): - if sid == exclude_source_id: - continue - learnings = get_learnings(sid) - if learnings: - baseline_concepts.extend(learnings.concepts) - baseline_tools.extend(learnings.tools) - # The excluded source's learnings become the "proposed" - excluded = get_learnings(exclude_source_id) - if excluded: - proposed_concepts = proposed_concepts + excluded.concepts - proposed_tools = proposed_tools + excluded.tools - else: - baseline_concepts, baseline_tools = aggregate_all_learnings() - - # --- Include declared skills (LinkedIn/manual) in baseline --- - from backend.services.user_skills import get_user_skills - - declared_skills = get_user_skills() - - # --- Current state --- - current_evidence = match_user_skill_evidence(baseline_concepts, baseline_tools) - for skill in declared_skills: - if skill not in current_evidence: - current_evidence[skill] = 1 - current_skills = set(current_evidence) - - # --- Proposed state (merge new on top of current) --- - merged_concepts = baseline_concepts + proposed_concepts - merged_tools = baseline_tools + proposed_tools - proposed_evidence = match_user_skill_evidence(merged_concepts, merged_tools) - for skill in declared_skills: - if skill not in proposed_evidence: - proposed_evidence[skill] = 1 - proposed_skills = set(proposed_evidence) - - patterns = load_patterns() - deltas: list[PatternDelta] = [] - - for pattern in patterns: - before = compute_pattern_fit(pattern, current_skills, current_evidence) - after = compute_pattern_fit(pattern, proposed_skills, proposed_evidence) - delta = round(after.score_pct - before.score_pct, 1) - - if delta == 0.0: - continue - - # Find newly covered concepts - before_covered = set(before.covered_concepts) - after_covered = set(after.covered_concepts) - new_covered = after_covered - before_covered - - required_set = set(pattern["required_concepts"]) - optional_set = set(pattern.get("optional_concepts", [])) - - deltas.append(PatternDelta( - pattern_id=pattern["id"], - name=pattern["name"], - before_pct=before.score_pct, - after_pct=after.score_pct, - delta_pct=delta, - new_required_covered=sorted(new_covered & required_set), - new_optional_covered=sorted(new_covered & optional_set), - )) - - deltas.sort(key=lambda d: (-d.delta_pct, d.pattern_id)) - improved = sum(1 for d in deltas if d.delta_pct > 0) - - return PatternImpactPreview( - deltas=deltas, - improved_count=improved, - unchanged_count=len(patterns) - len(deltas), - total_patterns=len(patterns), - ) diff --git a/backend/services/connections.py b/backend/services/connections.py index 757b9646..52d81285 100644 --- a/backend/services/connections.py +++ b/backend/services/connections.py @@ -75,7 +75,7 @@ def _parse_source_id(source_id: str) -> tuple[str | None, str | None]: """Parse source_id into (filter_key, filter_value) for ChromaDB queries.""" if source_id.startswith("yt_"): return "video_id", source_id[3:] - elif source_id.startswith("article_"): + elif source_id.startswith("article_") or source_id.startswith("job_"): return "source_id", source_id elif source_id.startswith("doc_"): return None, None @@ -102,9 +102,10 @@ def _source_id_from_metadata(meta: dict, namespace: str | None = None) -> tuple[ if "video_id" in meta: vid = meta["video_id"] return f"yt_{vid}", "youtube", meta.get("title", vid) - elif meta.get("source_type") == "article": + elif "source_type" in meta: sid = meta.get("source_id", "") - return sid, "article", meta.get("title", sid) + st = meta["source_type"] + return sid, st if st in ("article", "job") else "article", meta.get("title", sid) elif "file_path" in meta: fp = meta["file_path"] ids = _metadata_doc_ids(meta, namespace=namespace) @@ -386,6 +387,8 @@ def _get_type_from_id(source_id: str) -> SourceType: return "youtube" elif source_id.startswith("article_"): return "article" + elif source_id.startswith("job_"): + return "job" return "document" diff --git a/backend/services/infrastructure/db_migrations.py b/backend/services/infrastructure/db_migrations.py index dc6919af..9c37cce1 100644 --- a/backend/services/infrastructure/db_migrations.py +++ b/backend/services/infrastructure/db_migrations.py @@ -3026,6 +3026,212 @@ def _migration_086_drop_track_sort_order(conn: sqlite3.Connection) -> None: pass # Column already dropped +def _migration_087_signal_layer(conn: sqlite3.Connection) -> None: + """V1 signal layer: signals, signal_mappings, stack_items, curriculum_candidates, candidate_signals.""" + conn.execute(""" + CREATE TABLE IF NOT EXISTS signals ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source_id TEXT NOT NULL, + source_type TEXT NOT NULL DEFAULT 'learning', + signal_type TEXT NOT NULL CHECK(signal_type IN ('concept', 'tool')), + raw_value TEXT NOT NULL, + normalized_value TEXT NOT NULL, + confidence REAL NOT NULL DEFAULT 0.7, + status TEXT NOT NULL DEFAULT 'pending' + CHECK(status IN ('pending', 'mapped', 'stacked', 'discarded')), + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(source_id, signal_type, normalized_value) + ) + """) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_signals_normalized ON signals(normalized_value)" + ) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_signals_status ON signals(status)" + ) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_signals_source ON signals(source_id)" + ) + + conn.execute(""" + CREATE TABLE IF NOT EXISTS signal_mappings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + signal_id INTEGER NOT NULL, + target_type TEXT NOT NULL CHECK(target_type IN ('concept', 'stack_item')), + target_id TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(signal_id, target_type, target_id), + FOREIGN KEY (signal_id) REFERENCES signals(id) ON DELETE CASCADE + ) + """) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_signal_mappings_target ON signal_mappings(target_type, target_id)" + ) + + conn.execute(""" + CREATE TABLE IF NOT EXISTS stack_items ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + normalized_name TEXT NOT NULL UNIQUE, + frequency INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + """) + + conn.execute(""" + CREATE TABLE IF NOT EXISTS curriculum_candidates ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + normalized_name TEXT NOT NULL UNIQUE, + candidate_type TEXT NOT NULL DEFAULT 'concept', + frequency INTEGER NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'proposed' + CHECK(status IN ('proposed', 'accepted', 'rejected')), + created_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + """) + + conn.execute(""" + CREATE TABLE IF NOT EXISTS candidate_signals ( + candidate_id INTEGER NOT NULL, + signal_id INTEGER NOT NULL, + PRIMARY KEY (candidate_id, signal_id), + FOREIGN KEY (candidate_id) REFERENCES curriculum_candidates(id) ON DELETE CASCADE, + FOREIGN KEY (signal_id) REFERENCES signals(id) ON DELETE CASCADE + ) + """) + + +def _migration_088_drop_pattern_tables(conn: sqlite3.Connection) -> None: + """Drop decommissioned pattern tables. + + The pattern curation system (patterns, pattern_signals, pattern_diffs) + and the reference patterns seed table (ref_patterns) have been fully + removed from the codebase. No runtime code reads or writes these tables. + Also drops the pattern_id column from profile_skills (LEFT JOIN removed). + """ + conn.execute("PRAGMA foreign_keys = OFF") + for table in ("pattern_signals", "pattern_diffs", "patterns", "ref_patterns"): + conn.execute(f"DROP TABLE IF EXISTS {table}") + conn.execute("PRAGMA foreign_keys = ON") + + # Remove the now-orphan index that might linger after DROP TABLE + conn.execute("DROP INDEX IF EXISTS idx_pattern_signals_signal") + conn.execute("DROP INDEX IF EXISTS idx_pattern_diffs_job_id") + conn.execute("DROP INDEX IF EXISTS idx_ref_patterns_domain") + + +def _migration_089_source_extractions(conn: sqlite3.Connection) -> None: + """Extraction mirror tables: source_extractions + source_code_snippets. + + Dual-write target so ingest writes to both learning_* and these tables. + No readers switched yet — this is the safe first step toward removing + the learning_* tables. + """ + conn.execute(""" + CREATE TABLE IF NOT EXISTS source_extractions ( + source_id TEXT PRIMARY KEY, + summary TEXT, + confirmed_at TEXT, + composite_snapshot_json TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + """) + + conn.execute(""" + CREATE TABLE IF NOT EXISTS source_code_snippets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + source_id TEXT NOT NULL, + language TEXT NOT NULL, + code TEXT NOT NULL, + description TEXT NOT NULL, + position INTEGER NOT NULL, + verdict_status TEXT, + verdict_explanation TEXT, + verdict_corrected_code TEXT, + verdict_corrected_description TEXT, + FOREIGN KEY (source_id) REFERENCES source_extractions(source_id) ON DELETE CASCADE + ) + """) + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_source_code_snippets_source ON source_code_snippets(source_id)" + ) + + +def _migration_090_backfill_source_extractions(conn: sqlite3.Connection) -> None: + """Backfill source_extractions and source_code_snippets from learning_* tables. + + Ensures all existing sources have rows in the new tables before readers + switch away from learning_*. Signals already exist from backfill_signals.py. + """ + if _table_exists(conn, "learnings"): + conn.execute(""" + INSERT OR IGNORE INTO source_extractions + (source_id, summary, confirmed_at, composite_snapshot_json, created_at, updated_at) + SELECT source_id, summary, confirmed_at, composite_snapshot_json, + COALESCE(confirmed_at, datetime('now')), datetime('now') + FROM learnings + """) + + if _table_exists(conn, "learning_code_snippets"): + conn.execute(""" + INSERT OR IGNORE INTO source_code_snippets + (source_id, language, code, description, position, + verdict_status, verdict_explanation, + verdict_corrected_code, verdict_corrected_description) + SELECT lcs.source_id, lcs.language, lcs.code, lcs.description, lcs.position, + lcs.verdict_status, lcs.verdict_explanation, + lcs.verdict_corrected_code, lcs.verdict_corrected_description + FROM learning_code_snippets lcs + WHERE lcs.source_id IN (SELECT source_id FROM source_extractions) + """) + + +def _migration_090b_backfill_signals_from_learnings(conn: sqlite3.Connection) -> None: + """Backfill signals table from learning_concepts and learning_tools. + + Migration 090 backfilled source_extractions but not signals. + The new learnings_store readers query signals for concepts/tools, + so existing sources appear empty without this backfill. + Must run before 091 drops the legacy tables. + """ + if _table_exists(conn, "learning_concepts"): + conn.execute(""" + INSERT OR IGNORE INTO signals + (source_id, source_type, signal_type, raw_value, normalized_value, confidence, status) + SELECT lc.source_id, 'learning', 'concept', lc.concept, LOWER(TRIM(lc.concept)), 0.7, 'pending' + FROM learning_concepts lc + WHERE lc.source_id IN (SELECT source_id FROM source_extractions) + """) + + if _table_exists(conn, "learning_tools"): + conn.execute(""" + INSERT OR IGNORE INTO signals + (source_id, source_type, signal_type, raw_value, normalized_value, confidence, status) + SELECT lt.source_id, 'learning', 'tool', lt.tool, LOWER(TRIM(lt.tool)), 0.7, 'pending' + FROM learning_tools lt + WHERE lt.source_id IN (SELECT source_id FROM source_extractions) + """) + + +def _migration_091_drop_learning_tables(conn: sqlite3.Connection) -> None: + """Drop legacy learning_* tables. + + All readers and writers have migrated to source_extractions, + source_code_snippets, and signals. Migration 090 backfilled + existing data. These tables are now dead weight. + """ + for table in ( + "learning_code_snippets", + "learning_tools", + "learning_concepts", + "learnings", + ): + if _table_exists(conn, table): + conn.execute(f"DROP TABLE {table}") + + MIGRATIONS: tuple[MigrationStep, ...] = ( MigrationStep("0001", "chat_core_tables", _migration_001_chat_core), MigrationStep("0002", "messages_max_rerank_score", _migration_002_messages_max_rerank_score), @@ -3113,6 +3319,12 @@ def _migration_086_drop_track_sort_order(conn: sqlite3.Connection) -> None: MigrationStep("0084", "track_resources", _migration_084_track_resources), MigrationStep("0085", "track_categories", _migration_085_track_categories), MigrationStep("0086", "drop_track_sort_order", _migration_086_drop_track_sort_order), + MigrationStep("0087", "signal_layer", _migration_087_signal_layer), + MigrationStep("0088", "drop_pattern_tables", _migration_088_drop_pattern_tables), + MigrationStep("0089", "source_extractions", _migration_089_source_extractions), + MigrationStep("0090", "backfill_source_extractions", _migration_090_backfill_source_extractions), + MigrationStep("0090b", "backfill_signals_from_learnings", _migration_090b_backfill_signals_from_learnings), + MigrationStep("0091", "drop_learning_tables", _migration_091_drop_learning_tables), ) diff --git a/backend/services/ingest/learnings_store.py b/backend/services/ingest/learnings_store.py index 2ecb6fe1..c940d34d 100644 --- a/backend/services/ingest/learnings_store.py +++ b/backend/services/ingest/learnings_store.py @@ -1,10 +1,9 @@ """Learnings persistence backed by SQLite. -Replaces the old JSON-file store. On first access, any existing -data/learnings/*.json files are migrated into the DB automatically. +Stores extractions in source_extractions, signals, and source_code_snippets. Skill confirmation flow: - - Pipeline calls store_pending() → writes to DB with confirmed_at=NULL + - Pipeline calls store_pending() → writes with confirmed_at=NULL - Trace endpoint calls get_pending() → reads unconfirmed row - User confirms → confirm_learnings() sets confirmed_at - All other consumers (career, library, chat) call get_learnings() etc. @@ -13,140 +12,42 @@ import logging import sqlite3 -from pathlib import Path from backend.models.learnings import Learnings logger = logging.getLogger(__name__) -# Legacy path — only used for one-time migration -_LEGACY_DIR = Path("data/learnings") - - -def _get_conn(): - """Borrow the shared per-thread DB connection (WAL, migrations applied).""" - from backend.services.chat.history import get_db - - return get_db() - - -def _migrate_json_files() -> int: - """One-shot: move legacy JSON files into the learnings table.""" - if not _LEGACY_DIR.exists(): - return 0 - - json_files = list(_LEGACY_DIR.glob("*.json")) - if not json_files: - return 0 - - conn = _get_conn() - migrated = 0 - for path in json_files: - source_id = path.stem - try: - learnings = Learnings.model_validate_json(path.read_text()) - except Exception: - logger.warning("Skipping corrupt legacy learnings file: %s", path) - continue - - conn.execute( - "INSERT OR IGNORE INTO learnings (source_id, summary) VALUES (?, ?)", - (source_id, learnings.summary), - ) - for i, concept in enumerate(learnings.concepts): - conn.execute( - "INSERT OR IGNORE INTO learning_concepts (source_id, concept, position) VALUES (?, ?, ?)", - (source_id, concept, i), - ) - for i, tool in enumerate(learnings.tools): - conn.execute( - "INSERT OR IGNORE INTO learning_tools (source_id, tool, position) VALUES (?, ?, ?)", - (source_id, tool, i), - ) - for i, snippet in enumerate(learnings.code_snippets): - verdict = snippet.verdict - conn.execute( - """INSERT INTO learning_code_snippets - (source_id, language, code, description, position, - verdict_status, verdict_explanation, - verdict_corrected_code, verdict_corrected_description) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", - ( - source_id, - snippet.language, - snippet.code, - snippet.description, - i, - verdict.status if verdict else None, - verdict.explanation if verdict else None, - verdict.corrected_code if verdict else None, - verdict.corrected_description if verdict else None, - ), - ) - migrated += 1 - - conn.commit() - - if migrated: - logger.info("Migrated %d learnings from JSON to SQLite", migrated) - # Rename directory so migration doesn't re-run - backup = _LEGACY_DIR.with_name("learnings_json_backup") - try: - _LEGACY_DIR.rename(backup) - logger.info("Legacy JSON dir renamed to %s", backup) - except OSError: - logger.warning("Could not rename legacy dir; files will be ignored on next run") - - return migrated - - -_json_migrated = False - - -def _ensure_migrated(): - global _json_migrated - if _json_migrated: - return - _json_migrated = True - _migrate_json_files() - # --------------------------------------------------------------------------- -# Write helpers +# Write: source_extractions + source_code_snippets + signals # --------------------------------------------------------------------------- -def _upsert_learnings(source_id: str, learnings: Learnings, *, confirmed: bool) -> None: - """Internal: write learnings row + child tables.""" - _ensure_migrated() - conn = _get_conn() - - confirmed_at_expr = "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')" if confirmed else "NULL" +def _write_extraction( + conn: sqlite3.Connection, + source_id: str, + learnings: "Learnings", + *, + confirmed: bool, +) -> None: + """Write to source_extractions, source_code_snippets, and signals.""" + confirmed_at_expr = ( + "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')" if confirmed else "NULL" + ) conn.execute( - "INSERT OR REPLACE INTO learnings " - f"(source_id, summary, updated_at, confirmed_at) " - f"VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), {confirmed_at_expr})", + "INSERT OR REPLACE INTO source_extractions " + f"(source_id, summary, confirmed_at, updated_at) " + f"VALUES (?, ?, {confirmed_at_expr}, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", (source_id, learnings.summary), ) - # Write child tables (DELETE + INSERT for upsert) - conn.execute("DELETE FROM learning_concepts WHERE source_id = ?", (source_id,)) - conn.execute("DELETE FROM learning_tools WHERE source_id = ?", (source_id,)) - conn.execute("DELETE FROM learning_code_snippets WHERE source_id = ?", (source_id,)) - - for i, concept in enumerate(learnings.concepts): - conn.execute( - "INSERT OR IGNORE INTO learning_concepts (source_id, concept, position) VALUES (?, ?, ?)", - (source_id, concept, i), - ) - for i, tool in enumerate(learnings.tools): - conn.execute( - "INSERT OR IGNORE INTO learning_tools (source_id, tool, position) VALUES (?, ?, ?)", - (source_id, tool, i), - ) + # Re-insert code snippets (DELETE + INSERT for upsert) + conn.execute( + "DELETE FROM source_code_snippets WHERE source_id = ?", (source_id,) + ) for i, snippet in enumerate(learnings.code_snippets): verdict = snippet.verdict conn.execute( - """INSERT INTO learning_code_snippets + """INSERT INTO source_code_snippets (source_id, language, code, description, position, verdict_status, verdict_explanation, verdict_corrected_code, verdict_corrected_description) @@ -164,7 +65,34 @@ def _upsert_learnings(source_id: str, learnings: Learnings, *, confirmed: bool) ), ) - conn.commit() + # Write signals (concepts + tools) — DELETE + INSERT for upsert semantics + conn.execute( + "DELETE FROM signals WHERE source_id = ? AND source_type = 'learning'", + (source_id,), + ) + for concept in learnings.concepts: + normalized = concept.strip().lower() + conn.execute( + """INSERT INTO signals + (source_id, source_type, signal_type, raw_value, normalized_value, confidence, status) + VALUES (?, 'learning', 'concept', ?, ?, 0.7, 'pending')""", + (source_id, concept, normalized), + ) + for tool in learnings.tools: + normalized = tool.strip().lower() + conn.execute( + """INSERT INTO signals + (source_id, source_type, signal_type, raw_value, normalized_value, confidence, status) + VALUES (?, 'learning', 'tool', ?, ?, 0.7, 'pending')""", + (source_id, tool, normalized), + ) + + +def _get_conn(): + """Borrow the shared per-thread DB connection (WAL, migrations applied).""" + from backend.services.chat.history import get_db + + return get_db() # --------------------------------------------------------------------------- @@ -178,15 +106,16 @@ def store_pending(source_id: str, learnings: Learnings) -> None: in the DB but are invisible to career/library/chat queries until the user confirms via confirm_learnings(). """ - _upsert_learnings(source_id, learnings, confirmed=False) + conn = _get_conn() + _write_extraction(conn, source_id, learnings, confirmed=False) + conn.commit() def get_pending(source_id: str) -> Learnings | None: """Read unconfirmed learnings for a source, or None.""" - _ensure_migrated() conn = _get_conn() row = conn.execute( - "SELECT summary FROM learnings WHERE source_id = ? AND confirmed_at IS NULL", + "SELECT summary FROM source_extractions WHERE source_id = ? AND confirmed_at IS NULL", (source_id,), ).fetchone() if not row: @@ -201,7 +130,9 @@ def confirm_learnings(source_id: str, learnings: Learnings) -> None: the row with filtered concepts/tools and stamps confirmed_at. Also canonicalizes confirmed concepts into the concept learning layer. """ - _upsert_learnings(source_id, learnings, confirmed=True) + conn = _get_conn() + _write_extraction(conn, source_id, learnings, confirmed=True) + conn.commit() # Canonicalize confirmed concepts into the concept layer if learnings.concepts: @@ -243,18 +174,18 @@ def get_confirmed_learnings(source_id: str) -> dict | None: conn = get_db() row = conn.execute( - "SELECT confirmed_at FROM learnings WHERE source_id = ?", (source_id,) + "SELECT confirmed_at FROM source_extractions WHERE source_id = ?", (source_id,) ).fetchone() if not row or not row["confirmed_at"]: return None - concepts = [r["concept"] for r in conn.execute( - "SELECT concept FROM learning_concepts WHERE source_id = ? ORDER BY position", + concepts = [r["raw_value"] for r in conn.execute( + "SELECT raw_value FROM signals WHERE source_id = ? AND source_type = 'learning' AND signal_type = 'concept' ORDER BY id", (source_id,), ).fetchall()] - tools = [r["tool"] for r in conn.execute( - "SELECT tool FROM learning_tools WHERE source_id = ? ORDER BY position", + tools = [r["raw_value"] for r in conn.execute( + "SELECT raw_value FROM signals WHERE source_id = ? AND source_type = 'learning' AND signal_type = 'tool' ORDER BY id", (source_id,), ).fetchall()] @@ -263,18 +194,20 @@ def get_confirmed_learnings(source_id: str) -> dict | None: def save_learnings(source_id: str, learnings: Learnings) -> None: """Upsert learnings for a source as confirmed.""" - _upsert_learnings(source_id, learnings, confirmed=True) + conn = _get_conn() + _write_extraction(conn, source_id, learnings, confirmed=True) + conn.commit() def _load_learnings_children(conn, source_id: str, summary: str) -> Learnings: """Load concepts, tools, snippets for a given source_id.""" - concepts = [r["concept"] for r in conn.execute( - "SELECT concept FROM learning_concepts WHERE source_id = ? ORDER BY position", + concepts = [r["raw_value"] for r in conn.execute( + "SELECT raw_value FROM signals WHERE source_id = ? AND source_type = 'learning' AND signal_type = 'concept' ORDER BY id", (source_id,), ).fetchall()] - tools = [r["tool"] for r in conn.execute( - "SELECT tool FROM learning_tools WHERE source_id = ? ORDER BY position", + tools = [r["raw_value"] for r in conn.execute( + "SELECT raw_value FROM signals WHERE source_id = ? AND source_type = 'learning' AND signal_type = 'tool' ORDER BY id", (source_id,), ).fetchall()] @@ -282,7 +215,7 @@ def _load_learnings_children(conn, source_id: str, summary: str) -> Learnings: """SELECT language, code, description, verdict_status, verdict_explanation, verdict_corrected_code, verdict_corrected_description - FROM learning_code_snippets WHERE source_id = ? ORDER BY position""", + FROM source_code_snippets WHERE source_id = ? ORDER BY position""", (source_id,), ).fetchall() @@ -315,10 +248,9 @@ def _load_learnings_children(conn, source_id: str, summary: str) -> Learnings: def get_learnings(source_id: str) -> Learnings | None: """Get confirmed learnings for a source, or None.""" - _ensure_migrated() conn = _get_conn() row = conn.execute( - "SELECT summary FROM learnings WHERE source_id = ? AND confirmed_at IS NOT NULL", + "SELECT summary FROM source_extractions WHERE source_id = ? AND confirmed_at IS NOT NULL", (source_id,), ).fetchone() if not row: @@ -328,10 +260,16 @@ def get_learnings(source_id: str) -> Learnings | None: def delete_learnings(source_id: str) -> bool: """Delete learnings for a source. Returns True if a row was deleted.""" - _ensure_migrated() conn = _get_conn() cursor = conn.execute( - "DELETE FROM learnings WHERE source_id = ?", (source_id,) + "DELETE FROM source_extractions WHERE source_id = ?", (source_id,) + ) + conn.execute( + "DELETE FROM signals WHERE source_id = ? AND source_type = 'learning'", + (source_id,), + ) + conn.execute( + "DELETE FROM source_code_snippets WHERE source_id = ?", (source_id,) ) conn.commit() return cursor.rowcount > 0 @@ -339,11 +277,10 @@ def delete_learnings(source_id: str) -> bool: def list_all_learnings() -> list[str]: """Return all source IDs that have confirmed learnings.""" - _ensure_migrated() conn = _get_conn() try: rows = conn.execute( - "SELECT source_id FROM learnings WHERE confirmed_at IS NOT NULL" + "SELECT source_id FROM source_extractions WHERE confirmed_at IS NOT NULL" ).fetchall() except sqlite3.OperationalError: return [] @@ -351,58 +288,62 @@ def list_all_learnings() -> list[str]: def save_impact_snapshots(source_id: str, pattern_snapshot, composite_snapshot) -> None: - """Persist pattern and composite impact snapshots at confirmation time.""" - _ensure_migrated() + """Persist composite impact snapshot at confirmation time. + + pattern_snapshot is accepted for API compatibility but ignored — + pattern system has been removed. + """ conn = _get_conn() - pattern_json = pattern_snapshot.model_dump_json() if pattern_snapshot else None composite_json = composite_snapshot.model_dump_json() if composite_snapshot else None conn.execute( - "UPDATE learnings SET pattern_snapshot_json = ?, composite_snapshot_json = ? WHERE source_id = ?", - (pattern_json, composite_json, source_id), + "UPDATE source_extractions SET composite_snapshot_json = ? WHERE source_id = ?", + (composite_json, source_id), ) conn.commit() def get_impact_snapshots(source_id: str) -> tuple[dict | None, dict | None]: - """Read stored impact snapshots for a source. Returns (pattern_dict, composite_dict).""" + """Read stored impact snapshots for a source. Returns (pattern_dict, composite_dict). + + pattern_dict is always None — pattern system removed. + """ import json as _json - _ensure_migrated() conn = _get_conn() try: row = conn.execute( - "SELECT pattern_snapshot_json, composite_snapshot_json FROM learnings WHERE source_id = ?", + "SELECT composite_snapshot_json FROM source_extractions WHERE source_id = ?", (source_id,), ).fetchone() except Exception: return None, None if not row: return None, None - pattern = _json.loads(row["pattern_snapshot_json"]) if row["pattern_snapshot_json"] else None composite = _json.loads(row["composite_snapshot_json"]) if row["composite_snapshot_json"] else None - return pattern, composite + return None, composite def get_all_concepts_and_tools() -> tuple[list[str], list[str]]: """Return all concepts and tools from confirmed learnings (no N+1).""" - _ensure_migrated() conn = _get_conn() try: concept_rows = conn.execute( - """SELECT lc.concept FROM learning_concepts lc - JOIN learnings l ON l.source_id = lc.source_id - WHERE l.confirmed_at IS NOT NULL - ORDER BY lc.source_id, lc.position""" + """SELECT s.raw_value FROM signals s + JOIN source_extractions se ON se.source_id = s.source_id + WHERE se.confirmed_at IS NOT NULL + AND s.source_type = 'learning' AND s.signal_type = 'concept' + ORDER BY s.source_id, s.id""" ).fetchall() tool_rows = conn.execute( - """SELECT lt.tool FROM learning_tools lt - JOIN learnings l ON l.source_id = lt.source_id - WHERE l.confirmed_at IS NOT NULL - ORDER BY lt.source_id, lt.position""" + """SELECT s.raw_value FROM signals s + JOIN source_extractions se ON se.source_id = s.source_id + WHERE se.confirmed_at IS NOT NULL + AND s.source_type = 'learning' AND s.signal_type = 'tool' + ORDER BY s.source_id, s.id""" ).fetchall() except sqlite3.OperationalError: return [], [] return ( - [r["concept"] for r in concept_rows], - [r["tool"] for r in tool_rows], + [r["raw_value"] for r in concept_rows], + [r["raw_value"] for r in tool_rows], ) diff --git a/backend/services/ingest/pipeline.py b/backend/services/ingest/pipeline.py index b4a57348..1f5242c9 100644 --- a/backend/services/ingest/pipeline.py +++ b/backend/services/ingest/pipeline.py @@ -58,6 +58,64 @@ logger = logging.getLogger(__name__) +# --- Job text normalization (section prioritization / noise reduction) --- + +_JOB_BOILERPLATE_HEADINGS = re.compile( + r"^#+\s*(" + r"about\s+(?:us|the\s+company|our\s+team)" + r"|company\s+(?:overview|description|culture)" + r"|(?:our\s+)?benefits|perks" + r"|compensation(?:\s+(?:and|&)\s+benefits)?" + r"|equal\s+(?:opportunity|employment)" + r"|eeo\s+statement" + r"|how\s+to\s+apply" + r"|application\s+(?:process|instructions)" + r"|diversity(?:\s+(?:and|&)\s+inclusion)?" + r"|disclaimer" + r")\s*$", + re.IGNORECASE | re.MULTILINE, +) + + +def normalize_job_text(text: str) -> str: + """Strip boilerplate from job descriptions, keep signal-rich sections. + + This is section-level noise reduction, not true weighted scoring. + Removes common non-technical sections (About Us, Benefits, EEO, etc.) + and deduplicates repeated bullet points. + """ + lines = text.split("\n") + result: list[str] = [] + skip_section = False + seen_bullets: set[str] = set() + + for line in lines: + # Check if this line starts a boilerplate section + if _JOB_BOILERPLATE_HEADINGS.match(line.strip()): + skip_section = True + continue + + # A new heading (non-boilerplate) ends the skip + if skip_section and re.match(r"^#+\s+", line.strip()): + skip_section = False + + if skip_section: + continue + + # Deduplicate bullet points + stripped = line.strip() + if stripped.startswith(("-", "*", "•")) and len(stripped) > 3: + bullet_text = stripped.lstrip("-*• ").strip().lower() + if bullet_text in seen_bullets: + continue + seen_bullets.add(bullet_text) + + result.append(line) + + normalized = "\n".join(result).strip() + return normalized if normalized else text + + # Re-export for backward compatibility __all__ = [ "ArticleExtractionError", @@ -508,8 +566,13 @@ async def ingest_article( manual_title: str | None = None, manual_description: str | None = None, published_date: str | None = None, + source_type: str = "article", ) -> dict: - """Ingest a web article through the step pipeline. + """Ingest a text-based source through the step pipeline. + + The source_type parameter controls the source_id prefix and chunk + metadata source_type field. Defaults to "article" for backward + compatibility. Pass "job" for job postings or any future text type. Returns: dict with title, url, chunks_created, source_id @@ -525,7 +588,7 @@ def update(progress: int, step: str): if on_progress: on_progress(progress, step) - source_id = f"article_{hashlib.sha256(url.encode()).hexdigest()[:12]}" + source_id = f"{source_type}_{hashlib.sha256(url.encode()).hexdigest()[:12]}" # --- Acquire per-source lock to prevent concurrent duplicate ingestion --- lock = _acquire_source_lock(source_id) @@ -559,7 +622,7 @@ def update(progress: int, step: str): if "does not exist" not in str(e): logger.warning(f"Error checking for existing article: {e}") - _emit_safe(source_id, namespace, "source_submitted", "submit", job_id=job_id, payload_inline={"url": url, "content_type": "article", "namespace": namespace}) + _emit_safe(source_id, namespace, "source_submitted", "submit", job_id=job_id, payload_inline={"url": url, "content_type": source_type, "namespace": namespace}) # --- Fetch --- if manual_caption: @@ -676,6 +739,7 @@ def update(progress: int, step: str): title=fetch_result.title, source_url=url, source_id=source_id, + source_type=source_type, chunk_index=i, namespace=namespace, published_date=fetch_result.published_date, diff --git a/backend/services/ingest/steps/fetch.py b/backend/services/ingest/steps/fetch.py index 93a3fda4..5e917dc2 100644 --- a/backend/services/ingest/steps/fetch.py +++ b/backend/services/ingest/steps/fetch.py @@ -33,9 +33,15 @@ def _estimate_tokens(char_count: int) -> int: # Re-export YouTube helpers for backward compatibility __all__ = [ "ArticleExtractionError", + "BLOCKED_DOMAINS", "FetchResult", "FirecrawlArticleResult", + "InsufficientContentError", + "MIN_MANUAL_TEXT_LENGTH", + "UnsupportedSourceError", "build_timestamped_text", + "check_content_length", + "check_domain_policy", "download_pdf", "fetch_article", "fetch_article_with_firecrawl", @@ -61,6 +67,42 @@ def _estimate_tokens(char_count: int) -> int: ipaddress.ip_network("fe80::/10"), ] +BLOCKED_DOMAINS = ["linkedin.com"] + +MIN_MANUAL_TEXT_LENGTH = 500 + + +class UnsupportedSourceError(Exception): + """Raised when a URL belongs to a blocked domain.""" + + +class InsufficientContentError(Exception): + """Raised when extracted/pasted content is too short.""" + + +def check_domain_policy(url: str) -> None: + """Reject URLs from blocked domains (e.g. LinkedIn). + + Raises UnsupportedSourceError if the domain is blocked. + """ + parsed = urlparse(url) + hostname = (parsed.hostname or "").lower() + for domain in BLOCKED_DOMAINS: + if hostname == domain or hostname.endswith(f".{domain}"): + logger.warning("linkedin_url_blocked: url=%s timestamp=%s", url, time.strftime("%Y-%m-%dT%H:%M:%SZ")) + raise UnsupportedSourceError( + "LinkedIn URLs are not supported for automatic ingestion. " + "Please paste the job description manually." + ) + + +def check_content_length(text: str) -> None: + """Reject content shorter than MIN_MANUAL_TEXT_LENGTH characters.""" + if len(text.strip()) < MIN_MANUAL_TEXT_LENGTH: + raise InsufficientContentError( + "Extracted content too short. Please paste full content manually." + ) + def validate_url(url: str) -> None: """Reject URLs targeting internal/private networks (SSRF protection).""" @@ -298,8 +340,10 @@ def _fetch(params: dict | None = None): len(markdown), ) - if len(markdown) < 100: - raise ArticleExtractionError("Article content too short") + if len(markdown) < MIN_MANUAL_TEXT_LENGTH: + raise InsufficientContentError( + "Extracted content too short. Please paste full content manually." + ) return FirecrawlArticleResult( markdown=markdown, diff --git a/backend/services/job_profiles.py b/backend/services/job_profiles.py new file mode 100644 index 00000000..12a480d4 --- /dev/null +++ b/backend/services/job_profiles.py @@ -0,0 +1,29 @@ +"""Job profile loader with in-memory cache.""" + +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Any + +import yaml + +logger = logging.getLogger(__name__) + +_PROFILES_PATH = Path(__file__).resolve().parents[2] / "backend" / "config" / "job_profiles.yaml" + +_cache: dict[str, Any] | None = None + + +def load_job_profiles() -> dict[str, Any]: + """Load and cache job profiles from YAML.""" + global _cache + if _cache is not None: + return _cache + + with open(_PROFILES_PATH) as f: + data = yaml.safe_load(f) + + _cache = data["job_profiles"] + logger.info("Loaded %d job profiles", len(_cache)) + return _cache diff --git a/backend/services/learning/concept_store.py b/backend/services/learning/concept_store.py index 704c329a..ebd2d98f 100644 --- a/backend/services/learning/concept_store.py +++ b/backend/services/learning/concept_store.py @@ -1,11 +1,10 @@ """Canonical concept store for concept-centric learning. -Concepts are deduplicated from learning_concepts (the evidence layer). +Concepts are deduplicated from signals (the evidence layer). When learnings are confirmed, raw extracted terms are mapped to canonical concept nodes via aliases. Each concept tracks how many sources mention it. -This module does NOT own learning_concepts - that stays in learnings_store.py. -It reads learning_concepts to build the canonical layer above it. +This module reads signals + source_extractions to build the canonical layer. """ import logging @@ -92,15 +91,16 @@ def canonicalize_concepts(source_id: str, concepts: list[str]) -> list[str]: def _refresh_source_count(conn: sqlite3.Connection, concept_id: str) -> None: - """Recompute source_count from learning_concepts via aliases.""" + """Recompute source_count from signals via aliases.""" conn.execute( """UPDATE concepts SET source_count = ( - SELECT COUNT(DISTINCT lc.source_id) - FROM learning_concepts lc - JOIN learnings l ON l.source_id = lc.source_id - JOIN concept_aliases ca ON ca.alias = LOWER(TRIM(lc.concept)) + SELECT COUNT(DISTINCT s.source_id) + FROM signals s + JOIN source_extractions se ON se.source_id = s.source_id + JOIN concept_aliases ca ON ca.alias = LOWER(TRIM(s.raw_value)) WHERE ca.concept_id = ? - AND l.confirmed_at IS NOT NULL + AND s.source_type = 'learning' AND s.signal_type = 'concept' + AND se.confirmed_at IS NOT NULL ), updated_at = datetime('now') WHERE id = ?""", (concept_id, concept_id), @@ -145,13 +145,14 @@ def get_concept(concept_id: str) -> dict | None: # Source IDs that mention this concept (via aliases) source_rows = conn.execute( - """SELECT DISTINCT lc.source_id - FROM learning_concepts lc - JOIN learnings l ON l.source_id = lc.source_id - JOIN concept_aliases ca ON ca.alias = LOWER(TRIM(lc.concept)) + """SELECT DISTINCT s.source_id + FROM signals s + JOIN source_extractions se ON se.source_id = s.source_id + JOIN concept_aliases ca ON ca.alias = LOWER(TRIM(s.raw_value)) WHERE ca.concept_id = ? - AND l.confirmed_at IS NOT NULL - ORDER BY l.confirmed_at DESC""", + AND s.source_type = 'learning' AND s.signal_type = 'concept' + AND se.confirmed_at IS NOT NULL + ORDER BY se.confirmed_at DESC""", (concept_id,), ).fetchall() concept["source_ids"] = [r["source_id"] for r in source_rows] diff --git a/backend/services/learning/curriculum_loader.py b/backend/services/learning/curriculum_loader.py index d48aea77..1a9b40fa 100644 --- a/backend/services/learning/curriculum_loader.py +++ b/backend/services/learning/curriculum_loader.py @@ -7,7 +7,8 @@ Two track types: - concept: thin ordering layer over canonical concepts (validated against DB) - - resource: curated link/reference collections (no concept validation) + - resource: curated link/reference collections (may also include concepts) +Concept validation runs for ANY module with a concepts block, regardless of type. """ import difflib @@ -48,7 +49,8 @@ def validate_track( ) -> ValidationResult: """Validate a curriculum track against the canonical concept registry. - For resource tracks (track_type='resource'), concept validation is skipped. + Concept validation runs for ANY module that declares a concepts block, + regardless of track_type. Resource-only checks still apply to resource tracks. Returns a ValidationResult with blocking errors and non-blocking warnings. """ if conn is None: @@ -93,41 +95,49 @@ def validate_track( field=f"modules[{module.id}].title", )) - # Resource tracks skip concept validation entirely + # Resource tracks: warn if module has neither resources nor concepts if track.track_type == "resource": - # Validate resources instead for module in track.modules: if not module.resources and not module.concepts: warnings.append(ValidationWarning( message=f"Module '{module.id}' has no resources or concepts", field=f"modules[{module.id}]", )) - return ValidationResult(valid=len(errors) == 0, errors=errors, warnings=warnings) - # --- Concept track validation (existing logic) --- + # Concept-track-only checks (objective, time estimate, concepts required) + if track.track_type != "resource": + for module in track.modules: + if not module.objective: + errors.append(ValidationError( + message=f"Module '{module.id}' objective is missing", + field=f"modules[{module.id}].objective", + )) + if module.estimated_time_minutes <= 0: + errors.append(ValidationError( + message=f"Module '{module.id}' estimated_time_minutes must be > 0", + field=f"modules[{module.id}].estimated_time_minutes", + )) + if not module.concepts: + errors.append(ValidationError( + message=f"Module '{module.id}' has no concepts", + field=f"modules[{module.id}].concepts", + )) + + # --- Concept validation for ANY module with concepts, regardless of track_type --- all_concept_ids_in_track: dict[str, list[str]] = {} + # Only run concept validation if at least one module has concepts + has_any_concepts = any(m.concepts for m in track.modules) + if not has_any_concepts: + return ValidationResult(valid=len(errors) == 0, errors=errors, warnings=warnings) + # Pre-fetch all canonical concept IDs for fuzzy matching all_canonical_ids = [ r["id"] for r in conn.execute("SELECT id FROM concepts").fetchall() ] for module in track.modules: - if not module.objective: - errors.append(ValidationError( - message=f"Module '{module.id}' objective is missing", - field=f"modules[{module.id}].objective", - )) - if module.estimated_time_minutes <= 0: - errors.append(ValidationError( - message=f"Module '{module.id}' estimated_time_minutes must be > 0", - field=f"modules[{module.id}].estimated_time_minutes", - )) if not module.concepts: - errors.append(ValidationError( - message=f"Module '{module.id}' has no concepts", - field=f"modules[{module.id}].concepts", - )) continue # Concept-level checks diff --git a/backend/services/library_service.py b/backend/services/library_service.py index 5a43fa16..71c20abd 100644 --- a/backend/services/library_service.py +++ b/backend/services/library_service.py @@ -160,12 +160,13 @@ def _build_article_source( info: dict, feedback_stats: dict[str, dict], get_learnings_fn: Callable[[str], object | None], + source_type_override: str = "article", ) -> SourceItem: source_feedback = resolve_feedback(feedback_stats, source_id) return _finalize_source( SourceItem( id=source_id, - type="article", + type=source_type_override, title=info["title"], namespace=namespace, chunk_count=info["chunk_count"], @@ -222,7 +223,7 @@ def parse_source_lookup(source_id: str) -> tuple[str | None, str | None, bool]: """Parse source ID into (filter_key, filter_value, is_document).""" if source_id.startswith("yt_"): return "video_id", source_id[3:], False - if source_id.startswith("article_"): + if source_id.startswith("article_") or source_id.startswith("job_"): return "source_id", source_id, False if source_id.startswith("doc_"): return None, None, True @@ -325,17 +326,20 @@ def list_sources( ), ) - if source_type is None or source_type == "article": - for source_id, info in stats.get("articles", {}).items(): - all_sources.append( - _build_article_source( - namespace=ns, - source_id=source_id, - info=info, - feedback_stats=feedback_stats, - get_learnings_fn=get_learnings_fn, - ), - ) + for source_id, info in stats.get("typed_sources", stats.get("articles", {})).items(): + st = info.get("source_type", "article") + if source_type is not None and source_type != st: + continue + all_sources.append( + _build_article_source( + namespace=ns, + source_id=source_id, + info=info, + feedback_stats=feedback_stats, + get_learnings_fn=get_learnings_fn, + source_type_override=st, + ), + ) if source_type is None or source_type in {"document", "obsidian"}: for source_id, info in stats.get("documents", {}).items(): @@ -521,14 +525,18 @@ def get_source( ) continue - info = stats.get("articles", {}).get(filter_value) + # Look up in typed_sources (generic) with fallback to articles (backward compat) + typed = stats.get("typed_sources", stats.get("articles", {})) + info = typed.get(filter_value) if info: + st = info.get("source_type", "article") return _build_article_source( namespace=namespace, source_id=filter_value or "", info=info, feedback_stats=feedback_stats, get_learnings_fn=get_learnings_fn, + source_type_override=st, ) raise HTTPException(status_code=404, detail="Source not found") diff --git a/backend/services/patterns/__init__.py b/backend/services/patterns/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/services/patterns/clustering.py b/backend/services/patterns/clustering.py deleted file mode 100644 index 5dd24f23..00000000 --- a/backend/services/patterns/clustering.py +++ /dev/null @@ -1,432 +0,0 @@ -"""Pattern Curation Loop - incremental signal clustering. - -Assigns new signals to existing patterns via ChromaDB ANN search. -Signals that don't match any pattern go to an orphan pool. -When the orphan pool has >= 5 signals, attempts to form new proposed patterns. -""" - -from __future__ import annotations - -import json -import logging -import sqlite3 -import uuid -from datetime import UTC, datetime - -import chromadb -from langchain_openai import OpenAIEmbeddings - -from backend.config import get_settings -from backend.services.infrastructure.db import get_db - -logger = logging.getLogger(__name__) - -SIMILARITY_THRESHOLD = 0.72 -ORPHAN_PATTERN_ID = "_orphan" -ORPHAN_CLUSTER_MIN = 3 -ORPHAN_POOL_TRIGGER = 5 -ANN_K = 5 - -_chroma_client: chromadb.ClientAPI | None = None - - -def _get_chroma_client() -> chromadb.ClientAPI: - global _chroma_client - if _chroma_client is None: - settings = get_settings() - _chroma_client = chromadb.PersistentClient(path=settings.chroma_path) - return _chroma_client - - -def _get_signal_collection() -> chromadb.Collection: - client = _get_chroma_client() - return client.get_or_create_collection( - name="signal_embeddings", - metadata={"hnsw:space": "cosine"}, - ) - - -def _embed_signals(signals: list[str]) -> list[list[float]]: - settings = get_settings() - embeddings = OpenAIEmbeddings( - model=settings.embedding_model, - api_key=settings.openai_api_key, - ) - return embeddings.embed_documents(signals) - - -def _ensure_seed_signals_embedded() -> None: - """Embed all seed pattern signals if not already in the collection. - - Each unique signal text is embedded once. ChromaDB stores the embedding - keyed by ``signal::signal_type``. Pattern membership is resolved from - SQLite at query time (not from ChromaDB metadata) so that signals - belonging to multiple patterns are handled correctly. - """ - conn = get_db() - collection = _get_signal_collection() - - existing_ids = set(collection.get()["ids"]) if collection.count() > 0 else set() - - # Deduplicate: one embedding per unique (signal, signal_type) pair - rows = conn.execute( - """SELECT DISTINCT signal, signal_type FROM pattern_signals - WHERE pattern_id != ?""", - (ORPHAN_PATTERN_ID,), - ).fetchall() - - to_embed: list[str] = [] - to_ids: list[str] = [] - to_meta: list[dict] = [] - - for row in rows: - doc_id = f"{row['signal']}::{row['signal_type']}" - if doc_id in existing_ids: - continue - to_embed.append(row["signal"]) - to_ids.append(doc_id) - to_meta.append({"signal_type": row["signal_type"]}) - - if not to_embed: - return - - logger.info("Embedding %d seed signals into signal_embeddings collection", len(to_embed)) - vectors = _embed_signals(to_embed) - collection.add( - ids=to_ids, - embeddings=vectors, - documents=to_embed, - metadatas=to_meta, - ) - - -def _lookup_patterns_for_signal( - conn: sqlite3.Connection, - signal: str, - signal_type: str, -) -> list[str]: - """Return all non-orphan pattern_ids that contain this signal.""" - rows = conn.execute( - """SELECT pattern_id FROM pattern_signals - WHERE signal = ? AND signal_type = ? AND pattern_id != ?""", - (signal, signal_type, ORPHAN_PATTERN_ID), - ).fetchall() - return [r["pattern_id"] for r in rows] - - -def assign_signals_to_patterns( - signals: list[tuple[str, str]], - source_id: str | None = None, -) -> dict: - """Incrementally assign signals to patterns via ANN lookup. - - Args: - signals: list of (signal_name, signal_type) tuples - source_id: optional source_id that produced these signals - - Returns: - dict with keys: assigned (pattern_id -> [signals]), orphaned ([signals]) - """ - _ensure_seed_signals_embedded() - - conn = get_db() - collection = _get_signal_collection() - assigned: dict[str, list[str]] = {} - orphaned: list[str] = [] - - if not signals: - return {"assigned": assigned, "orphaned": orphaned} - - signal_texts = [s[0] for s in signals] - vectors = _embed_signals(signal_texts) - - for i, (signal_name, signal_type) in enumerate(signals): - doc_id = f"{signal_name}::{signal_type}" - - # Check if already in a non-orphan pattern - existing_pids = _lookup_patterns_for_signal(conn, signal_name, signal_type) - if existing_pids: - for pid in existing_pids: - assigned.setdefault(pid, []).append(signal_name) - continue - - # ANN query for nearest neighbors - results = collection.query( - query_embeddings=[vectors[i]], - n_results=min(ANN_K, max(collection.count(), 1)), - include=["metadatas", "distances", "documents"], - ) - - # Resolve pattern membership from SQLite, not ChromaDB metadata. - # A neighbor signal like "AI Agents" may belong to 5 seed patterns; - # ChromaDB metadata only stores one. SQLite is the source of truth. - matched_patterns: set[str] = set() - if results["distances"] and results["distances"][0]: - for dist, meta, doc in zip( - results["distances"][0], - results["metadatas"][0], - results["documents"][0], - ): - # ChromaDB cosine distance: 0 = identical, 2 = opposite - similarity = 1.0 - dist - if similarity >= SIMILARITY_THRESHOLD: - neighbor_signal = doc - neighbor_type = meta["signal_type"] - pids = _lookup_patterns_for_signal(conn, neighbor_signal, neighbor_type) - matched_patterns.update(pids) - - if matched_patterns: - for pid in matched_patterns: - conn.execute( - """INSERT OR IGNORE INTO pattern_signals - (pattern_id, signal, signal_type, strength, source_id) - VALUES (?, ?, ?, ?, ?)""", - (pid, signal_name, signal_type, 0.8, source_id), - ) - assigned.setdefault(pid, []).append(signal_name) - else: - conn.execute( - """INSERT OR IGNORE INTO pattern_signals - (pattern_id, signal, signal_type, strength, source_id) - VALUES (?, ?, ?, 0.5, ?)""", - (ORPHAN_PATTERN_ID, signal_name, signal_type, source_id), - ) - orphaned.append(signal_name) - - # Add to embedding collection - existing_in_coll = collection.get(ids=[doc_id]) - if not existing_in_coll["ids"]: - collection.add( - ids=[doc_id], - embeddings=[vectors[i]], - documents=[signal_name], - metadatas=[{"signal_type": signal_type}], - ) - - conn.commit() - - # Check orphan pool - _maybe_cluster_orphans(conn, collection) - - return {"assigned": assigned, "orphaned": orphaned} - - -def _maybe_cluster_orphans(conn: sqlite3.Connection, collection: chromadb.Collection) -> None: - """If orphan pool has >= ORPHAN_POOL_TRIGGER signals, attempt clustering.""" - orphan_rows = conn.execute( - """SELECT signal, signal_type FROM pattern_signals - WHERE pattern_id = ?""", - (ORPHAN_PATTERN_ID,), - ).fetchall() - - if len(orphan_rows) < ORPHAN_POOL_TRIGGER: - return - - logger.info("Orphan pool has %d signals, attempting clustering", len(orphan_rows)) - - orphan_signals = [(r["signal"], r["signal_type"]) for r in orphan_rows] - orphan_texts = [s[0] for s in orphan_signals] - vectors = _embed_signals(orphan_texts) - - # Pairwise similarity (small matrix - only orphans) - n = len(vectors) - adjacency: dict[int, set[int]] = {i: set() for i in range(n)} - for i in range(n): - for j in range(i + 1, n): - sim = _cosine_similarity(vectors[i], vectors[j]) - if sim >= SIMILARITY_THRESHOLD: - adjacency[i].add(j) - adjacency[j].add(i) - - # Connected components - visited: set[int] = set() - clusters: list[list[int]] = [] - for i in range(n): - if i in visited: - continue - component: list[int] = [] - stack = [i] - while stack: - node = stack.pop() - if node in visited: - continue - visited.add(node) - component.append(node) - stack.extend(adjacency[node] - visited) - if len(component) >= ORPHAN_CLUSTER_MIN: - clusters.append(component) - - now = datetime.now(UTC).isoformat() - for cluster_indices in clusters: - pattern_id = f"pat_discovered_{uuid.uuid4().hex[:12]}" - cluster_signals = [orphan_signals[i] for i in cluster_indices] - signal_names = [s[0] for s in cluster_signals] - - conn.execute( - """INSERT INTO patterns (id, name, description, status, source_type, created_at) - VALUES (?, ?, ?, 'proposed', 'discovered', ?)""", - ( - pattern_id, - f"Cluster ({len(cluster_signals)} signals)", - f"Auto-discovered cluster: {', '.join(signal_names[:5])}", - now, - ), - ) - - for idx in cluster_indices: - sig_name, sig_type = orphan_signals[idx] - conn.execute( - """DELETE FROM pattern_signals - WHERE pattern_id = ? AND signal = ? AND signal_type = ?""", - (ORPHAN_PATTERN_ID, sig_name, sig_type), - ) - conn.execute( - """INSERT OR IGNORE INTO pattern_signals - (pattern_id, signal, signal_type, strength, source_id) - VALUES (?, ?, ?, 0.7, NULL)""", - (pattern_id, sig_name, sig_type), - ) - - conn.commit() - if clusters: - logger.info("Created %d new proposed patterns from orphan pool", len(clusters)) - - -def _cosine_similarity(a: list[float], b: list[float]) -> float: - dot = sum(x * y for x, y in zip(a, b)) - norm_a = sum(x * x for x in a) ** 0.5 - norm_b = sum(x * x for x in b) ** 0.5 - if norm_a == 0 or norm_b == 0: - return 0.0 - return dot / (norm_a * norm_b) - - -def compute_pattern_diff( - job_id: str, - source_id: str, - confirmed_concepts: list[str], - confirmed_tools: list[str], -) -> dict: - """Run incremental clustering for newly confirmed signals and compute diff. - - Returns the diff dict stored in pattern_diffs. - """ - conn = get_db() - - # Snapshot before - before = _pattern_signal_counts(conn) - - # Build signal list - signals: list[tuple[str, str]] = [] - for concept in confirmed_concepts: - signals.append((concept, "concept")) - for tool in confirmed_tools: - signals.append((tool, "tool")) - - # Run incremental assignment - result = assign_signals_to_patterns(signals, source_id=source_id) - - # Snapshot after - after = _pattern_signal_counts(conn) - - # Compute diff - new_patterns: list[dict] = [] - strengthened: list[dict] = [] - - all_pattern_ids = set(before.keys()) | set(after.keys()) - for pid in all_pattern_ids: - if pid == ORPHAN_PATTERN_ID: - continue - before_count = before.get(pid, 0) - after_count = after.get(pid, 0) - if before_count == 0 and after_count > 0: - row = conn.execute( - "SELECT name, status FROM patterns WHERE id = ?", (pid,) - ).fetchone() - if row: - new_patterns.append({ - "pattern_id": pid, - "name": row["name"], - "status": row["status"], - "signal_count": after_count, - "new_signals": result["assigned"].get(pid, []), - }) - elif after_count > before_count: - row = conn.execute( - "SELECT name, status FROM patterns WHERE id = ?", (pid,) - ).fetchone() - if row: - strengthened.append({ - "pattern_id": pid, - "name": row["name"], - "status": row["status"], - "before_count": before_count, - "after_count": after_count, - "new_signals": result["assigned"].get(pid, []), - }) - - diff = { - "new_patterns": new_patterns, - "strengthened": strengthened, - "orphaned": result["orphaned"], - "total_signals": len(signals), - } - - # Store diff - conn.execute( - """INSERT INTO pattern_diffs (job_id, source_id, diff_json, created_at) - VALUES (?, ?, ?, ?)""", - (job_id, source_id, json.dumps(diff), datetime.now(UTC).isoformat()), - ) - conn.commit() - - return diff - - -def _pattern_signal_counts(conn: sqlite3.Connection) -> dict[str, int]: - rows = conn.execute( - "SELECT pattern_id, COUNT(*) as cnt FROM pattern_signals GROUP BY pattern_id" - ).fetchall() - return {row["pattern_id"]: row["cnt"] for row in rows} - - -def get_pattern_strength(conn: sqlite3.Connection, pattern_id: str) -> dict: - """Compute pattern strength on read (not stored).""" - signal_count = conn.execute( - "SELECT COUNT(*) as cnt FROM pattern_signals WHERE pattern_id = ?", - (pattern_id,), - ).fetchone()["cnt"] - - source_count = conn.execute( - """SELECT COUNT(DISTINCT source_id) as cnt FROM pattern_signals - WHERE pattern_id = ? AND source_id IS NOT NULL""", - (pattern_id,), - ).fetchone()["cnt"] - - avg_strength = conn.execute( - "SELECT AVG(strength) as avg FROM pattern_signals WHERE pattern_id = ?", - (pattern_id,), - ).fetchone()["avg"] or 0.0 - - strength = ( - 0.4 * min(signal_count / 15, 1.0) - + 0.3 * min(source_count / 8, 1.0) - + 0.3 * avg_strength - ) - - if signal_count >= 15 and source_count >= 8: - maturity = "core" - elif signal_count >= 8 and source_count >= 4: - maturity = "established" - elif signal_count >= 3 and source_count >= 2: - maturity = "emerging" - else: - maturity = "nascent" - - return { - "strength": round(strength, 3), - "maturity": maturity, - "signal_count": signal_count, - "source_count": source_count, - "avg_centroid_similarity": round(avg_strength, 3), - } diff --git a/backend/services/profile.py b/backend/services/profile.py index 4da92cd9..9dd2f82d 100644 --- a/backend/services/profile.py +++ b/backend/services/profile.py @@ -1,7 +1,6 @@ """Profile / positioning layer service. Core invariant: engine suggests, user confirms, history records. -Profile reads from patterns/evidence but never writes upstream. """ from __future__ import annotations @@ -140,9 +139,8 @@ def mark_profile_reviewed() -> dict: def list_profile_skills() -> list[dict]: conn = _get_conn() rows = conn.execute( - """SELECT ps.*, p.name as pattern_name + """SELECT ps.*, NULL as pattern_name FROM profile_skills ps - LEFT JOIN patterns p ON ps.pattern_id = p.id ORDER BY ps.is_pinned DESC, ps.support_score DESC, ps.skill_name""", ).fetchall() return [dict(r) for r in rows] @@ -166,8 +164,7 @@ def add_profile_skill(skill_name: str, source: str = "manual", visibility_status ) conn.commit() row = conn.execute( - """SELECT ps.*, p.name as pattern_name FROM profile_skills ps - LEFT JOIN patterns p ON ps.pattern_id = p.id WHERE ps.id = ?""", + """SELECT ps.*, NULL as pattern_name FROM profile_skills ps WHERE ps.id = ?""", (skill_id,), ).fetchone() return dict(row) @@ -230,8 +227,7 @@ def update_profile_skill(skill_id: str, updates: dict) -> dict | None: conn.commit() updated = conn.execute( - """SELECT ps.*, p.name as pattern_name FROM profile_skills ps - LEFT JOIN patterns p ON ps.pattern_id = p.id WHERE ps.id = ?""", + """SELECT ps.*, NULL as pattern_name FROM profile_skills ps WHERE ps.id = ?""", (skill_id,), ).fetchone() return dict(updated) @@ -258,53 +254,11 @@ def remove_profile_skill(skill_id: str) -> bool: def get_suggested_skills() -> list[dict]: - """Derive skill suggestions from confirmed patterns. + """Derive skill suggestions (stub — pattern system removed). - Returns patterns that are strong enough to suggest but not yet in the profile. + Returns an empty list. Previously derived from confirmed patterns. """ - conn = _get_conn() - from backend.services.patterns.clustering import get_pattern_strength - - # Get all confirmed patterns - patterns = conn.execute( - "SELECT * FROM patterns WHERE status = 'confirmed' ORDER BY name", - ).fetchall() - - # Get existing profile skill pattern_ids to avoid duplicates - existing = conn.execute( - "SELECT pattern_id, skill_name FROM profile_skills WHERE pattern_id IS NOT NULL", - ).fetchall() - existing_pattern_ids = {r["pattern_id"] for r in existing} - existing_skill_names = {r["skill_name"].lower() for r in existing} - - suggestions = [] - for pattern in patterns: - strength_info = get_pattern_strength(conn, pattern["id"]) - - # Suggest patterns that are at least emerging, or seed patterns with enough signals - is_seed = pattern["source_type"] == "seed" - if strength_info["maturity"] == "nascent" and not (is_seed and strength_info["signal_count"] >= 5): - continue - - already = ( - pattern["id"] in existing_pattern_ids - or pattern["name"].lower() in existing_skill_names - ) - - suggestions.append({ - "pattern_id": pattern["id"], - "pattern_name": pattern["name"], - "suggested_label": pattern["name"], - "support_score": strength_info["strength"], - "maturity": strength_info["maturity"], - "signal_count": strength_info["signal_count"], - "source_count": strength_info["source_count"], - "already_in_profile": already, - }) - - # Sort: not-in-profile first, then by strength descending - suggestions.sort(key=lambda s: (s["already_in_profile"], -s["support_score"])) - return suggestions + return [] # --------------------------------------------------------------------------- diff --git a/backend/services/retrieval/retriever.py b/backend/services/retrieval/retriever.py index 03ab6481..1829e07e 100644 --- a/backend/services/retrieval/retriever.py +++ b/backend/services/retrieval/retriever.py @@ -51,6 +51,7 @@ def _empty_namespace_stats(namespace: str) -> dict: "videos": {}, "documents": {}, "articles": {}, + "typed_sources": {}, } @@ -158,7 +159,8 @@ def _get_namespace_stats_uncached(namespace: str) -> dict: videos = {} documents = {} - articles = {} + # Generic bucket for any source_type-tagged content (articles, jobs, etc.) + typed_sources: dict[str, dict] = {} for metadata in all_data["metadatas"]: if not metadata: @@ -189,12 +191,14 @@ def _get_namespace_stats_uncached(namespace: str) -> dict: videos[vid]["taxonomy_confidence"] = metadata.get("taxonomy_confidence") videos[vid]["chunk_count"] += 1 - elif metadata.get("source_type") == "article": + elif "source_type" in metadata: + st = metadata["source_type"] sid = metadata.get("source_id", "") - if sid and sid not in articles: - articles[sid] = { + if sid and sid not in typed_sources: + typed_sources[sid] = { "title": metadata.get("title", sid), "source_url": metadata.get("source_url", ""), + "source_type": st, "chunk_count": 0, "indexed_at": metadata.get("indexed_at"), "published_date": metadata.get("published_date", ""), @@ -203,14 +207,14 @@ def _get_namespace_stats_uncached(namespace: str) -> dict: "taxonomy_path": metadata.get("taxonomy_path"), "taxonomy_confidence": metadata.get("taxonomy_confidence"), } - if sid and not articles[sid].get("taxonomy_labels"): - articles[sid]["taxonomy_labels"] = metadata.get("taxonomy_labels", "") - if sid and not articles[sid].get("taxonomy_path"): - articles[sid]["taxonomy_path"] = metadata.get("taxonomy_path") - if sid and articles[sid].get("taxonomy_confidence") is None: - articles[sid]["taxonomy_confidence"] = metadata.get("taxonomy_confidence") + if sid and not typed_sources[sid].get("taxonomy_labels"): + typed_sources[sid]["taxonomy_labels"] = metadata.get("taxonomy_labels", "") + if sid and not typed_sources[sid].get("taxonomy_path"): + typed_sources[sid]["taxonomy_path"] = metadata.get("taxonomy_path") + if sid and typed_sources[sid].get("taxonomy_confidence") is None: + typed_sources[sid]["taxonomy_confidence"] = metadata.get("taxonomy_confidence") if sid: - articles[sid]["chunk_count"] += 1 + typed_sources[sid]["chunk_count"] += 1 elif "file_path" in metadata: fp = metadata["file_path"] @@ -242,6 +246,9 @@ def _get_namespace_stats_uncached(namespace: str) -> dict: documents[sid]["taxonomy_confidence"] = metadata.get("taxonomy_confidence") documents[sid]["chunk_count"] += 1 + # Backward-compatible: expose articles as filtered view of typed_sources + articles = {sid: info for sid, info in typed_sources.items() if info.get("source_type") == "article"} + return { "name": namespace, "chunk_count": count, @@ -251,6 +258,7 @@ def _get_namespace_stats_uncached(namespace: str) -> dict: "videos": videos, "documents": documents, "articles": articles, + "typed_sources": typed_sources, } diff --git a/curriculum/tracks/_prep-displaced.yaml b/curriculum/tracks/_prep-displaced.yaml index ba40dbe4..b506ff7b 100644 --- a/curriculum/tracks/_prep-displaced.yaml +++ b/curriculum/tracks/_prep-displaced.yaml @@ -144,7 +144,7 @@ objective: > Prep for the next wave of AI roles - world models, sim-to-real, 3D vision, and physical AI. color: "#ffc47c" - target_track: embodied-ai + target_track: applied-systems resources: - name: NVIDIA Physical AI url: https://developer.nvidia.com/physical-ai diff --git a/curriculum/tracks/ai-engineering.yaml b/curriculum/tracks/ai-engineering.yaml index 79e3ffec..1964203f 100644 --- a/curriculum/tracks/ai-engineering.yaml +++ b/curriculum/tracks/ai-engineering.yaml @@ -1,7 +1,10 @@ id: ai-engineering title: AI Engineering description: > - Build production AI systems - from inference to agents to evals. + How AI applications are built, orchestrated, grounded, evaluated, and operated. + Progression: model foundations, prompting control, retrieval grounding, + agent orchestration, memory and state, tool integration, evaluation, + and production deployment. difficulty: intermediate track_type: resource category: systems @@ -9,33 +12,52 @@ category_order: 2 track_order: 2 modules: - - id: inference - title: Inference Infrastructure + # ── Module 1: LLM Foundations ────────────────────────────────────────── + # Concept anchors: transformer-architecture, tokenization, inference-serving, + # quantization, parameter-efficient-fine-tuning, alignment + # Teaches: what large language models are, how they serve requests, + # how quantization trades accuracy for speed, how fine-tuning adapts + # a foundation model, and how alignment shapes model behavior. + - id: llm_foundations + title: LLM Foundations objective: > - Learn how to serve models fast and cheap - from quantization to speculative decoding. + Understand what large language models are and how they run - inference + serving and batching, quantization tradeoffs, parameter-efficient + fine-tuning, and the alignment techniques that shape model behavior. color: "#55cdff" sort_order: 1 + concepts: + - concept_id: transformer-inference + sort_order: 1 + - concept_id: quantization + sort_order: 2 + - concept_id: fine-tuning + sort_order: 3 + - concept_id: alignment + sort_order: 4 + - concept_id: multimodal-models + sort_order: 5 resources: - - name: vLLM - url: https://github.com/vllm-project/vllm + - name: GPT-4o + url: https://openai.com/index/hello-gpt-4o/ description: > - High-throughput LLM serving with PagedAttention, continuous batching, and speculative decoding. + Natively multimodal model processing text, images, and audio in a unified architecture. detail: > - The default choice for self-hosted inference. Study PagedAttention for memory-efficient KV-cache management. + The production standard for frontier LLMs. Study how unified tokenization enables cross-modal reasoning. sort_order: 1 - - name: NVIDIA TensorRT-LLM - url: https://github.com/NVIDIA/TensorRT-LLM + - name: Google Gemini + url: https://deepmind.google/technologies/gemini/ description: > - NVIDIA's optimized inference library with FP8 quantization, in-flight batching, and multi-GPU tensor parallelism. + Natively multimodal with long-context (1M+ tokens), video understanding, and code generation. detail: > - Best raw performance on NVIDIA hardware. Compare latency/throughput vs vLLM for production decisions. + Study the long-context pattern: how processing entire documents in a single call changes retrieval tradeoffs. sort_order: 2 - - name: SGLang - url: https://github.com/sgl-project/sglang + - name: vLLM + url: https://github.com/vllm-project/vllm description: > - Fast structured generation with RadixAttention for prefix caching and constrained decoding. + High-throughput LLM serving with PagedAttention, continuous batching, and speculative decoding. detail: > - Study RadixAttention for KV-cache sharing across requests. Ideal for JSON/schema-constrained output. + The default for self-hosted inference. Study PagedAttention for memory-efficient KV-cache management. sort_order: 3 - name: llama.cpp url: https://github.com/ggerganov/llama.cpp @@ -44,127 +66,111 @@ modules: detail: > Essential for edge/local deployment. Understand quantization levels (Q4_K_M, Q5_K_S) and their accuracy tradeoffs. sort_order: 4 - - name: Ollama - url: https://ollama.com + - name: Hugging Face PEFT + url: https://github.com/huggingface/peft description: > - Developer-friendly local LLM runner wrapping llama.cpp with Docker-like model management. + Parameter-efficient fine-tuning with LoRA, QLoRA, prefix tuning, and adapter methods. detail: > - Fast prototyping tool. Use for local dev loops, then graduate to vLLM/TensorRT-LLM for production. + Start here for any fine-tuning task. LoRA + 4-bit quantization (QLoRA) runs on a single consumer GPU. sort_order: 5 - - name: Hugging Face TGI - url: https://github.com/huggingface/text-generation-inference + - name: Anthropic Constitutional AI + url: https://www.anthropic.com/research/constitutional-ai-harmlessness-from-ai-feedback description: > - Production-ready inference server with flash attention, quantization, and watermarking support. + Self-supervised alignment where the model critiques and revises its own outputs against principles. detail: > - Good middle ground between ease-of-use and performance. Native HF model hub integration. + Study the principle-based alignment approach: how to define behavioral constraints without human labeling. sort_order: 6 - - id: agents - title: Agent Architecture + # ── Module 2: Prompting & Control ────────────────────────────────────── + # Concept anchors: chain-of-thought, test-time-compute, self-consistency, + # reasoning-traces, prompt-evaluation, structured-output + # Teaches: how to control LLM behavior through prompting - reasoning + # chains, test-time compute scaling, sampling strategies, and how to + # systematically evaluate prompt quality. + - id: prompting_control + title: Prompting & Control objective: > - Build agents that actually work - tool use, memory, planning, and multi-agent patterns. + Master the techniques that control LLM behavior - chain-of-thought + reasoning, test-time compute scaling, self-consistency via sampling, + transparent reasoning traces, and systematic prompt evaluation. color: "#ffc47c" sort_order: 2 - resources: - - name: LangGraph - url: https://github.com/langchain-ai/langgraph - description: > - Graph-based agent orchestration with cycles, persistence, and human-in-the-loop patterns. - detail: > - The most mature agent framework. Study the state machine pattern for reliable multi-step workflows. + concepts: + - concept_id: prompt-structure sort_order: 1 - - name: CrewAI - url: https://github.com/crewAIInc/crewAI - description: > - Multi-agent framework with role-based agents, task delegation, and sequential/parallel execution. - detail: > - Good for rapid prototyping of multi-agent systems. Compare the role-play pattern vs LangGraph's graph approach. + - concept_id: context-window-management sort_order: 2 - - name: AutoGen (Microsoft) - url: https://github.com/microsoft/autogen - description: > - Multi-agent conversation framework with customizable agents, code execution, and group chat patterns. - detail: > - Study the conversational agent pattern and how it handles tool use, code generation, and verification. + - concept_id: self-consistency sort_order: 3 - - name: Anthropic Claude Tool Use - url: https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview - description: > - Native function calling with structured outputs, parallel tool use, and streaming tool results. - detail: > - Master the tool-use protocol: schema definition, forced tool calls, and error handling patterns. + - concept_id: prompt-evaluation sort_order: 4 - - name: OpenAI Agents SDK - url: https://github.com/openai/openai-agents-python - description: > - Official agent framework with handoffs, guardrails, tracing, and multi-agent orchestration. - detail: > - Study the handoff pattern for agent-to-agent delegation and the built-in tracing for debugging. + - concept_id: control-surface sort_order: 5 - - name: Pydantic AI - url: https://ai.pydantic.dev - description: > - Type-safe agent framework with structured outputs, dependency injection, and model-agnostic design. - detail: > - Best for Python-heavy teams wanting type safety. The structured output + validation pattern is production-grade. - sort_order: 6 - - - id: evals - title: Evals & Observability - objective: > - Know when your LLM system is broken before your users do. - color: "#5bb86e" - sort_order: 3 resources: - - name: Braintrust - url: https://www.braintrust.dev + - name: OpenAI o1 / o3 + url: https://openai.com/index/learning-to-reason-with-llms/ description: > - End-to-end LLM eval platform with scoring, experiments, datasets, and production logging. + Chain-of-thought reasoning models with internal deliberation for math, science, and coding. detail: > - Study the eval-driven development loop: define metrics, run experiments, compare prompts/models systematically. + Study the test-time compute scaling paradigm: spending more inference tokens improves reasoning quality. sort_order: 1 - - name: LangSmith - url: https://smith.langchain.com + - name: Claude Extended Thinking + url: https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking description: > - Tracing, evaluation, and monitoring platform for LLM applications with dataset management. + Explicit thinking traces in Claude for complex analysis, planning, and multi-step problem solving. detail: > - Use for tracing complex chains/agents. The trace visualization is invaluable for debugging multi-step flows. + Study how exposing the reasoning chain improves transparency and enables better prompt engineering. sort_order: 2 - - name: Arize Phoenix - url: https://github.com/Arize-ai/phoenix + - name: DeepSeek-R1 + url: https://github.com/deepseek-ai/DeepSeek-R1 description: > - Open-source LLM observability with tracing, evals, embeddings analysis, and retrieval diagnostics. + Open-weight reasoning model trained with RL to generate detailed chain-of-thought before answering. detail: > - Best open-source option. Study the embedding drift detection and retrieval quality metrics. + The open-source reasoning frontier. Study the RL training recipe: how reinforcement learning teaches reasoning. sort_order: 3 - - name: RAGAS - url: https://github.com/explodinggradients/ragas + - name: Self-Consistency (Wang et al.) + url: https://arxiv.org/abs/2203.11171 description: > - RAG evaluation framework measuring faithfulness, answer relevancy, context precision, and recall. + Sample multiple reasoning paths and take majority vote. Simple technique that reliably boosts accuracy. detail: > - Essential for any RAG system. Implement the four core metrics as CI gates for retrieval quality. + The cheapest reasoning improvement: sample N completions, majority-vote the answer. Works with any model. sort_order: 4 - - name: Guardrails AI - url: https://github.com/guardrails-ai/guardrails - description: > - Input/output validation framework with validators for PII, toxicity, hallucination, and schema compliance. - detail: > - Production safety layer. Study the validator composability and how to build custom domain validators. - sort_order: 5 - name: Promptfoo url: https://github.com/promptfoo/promptfoo description: > CLI-first eval tool for testing prompts against datasets with assertions and model comparison. detail: > Fast iteration tool for prompt engineering. Run prompt A/B tests before deploying changes. - sort_order: 6 + sort_order: 5 - - id: retrieval - title: RAG & Retrieval + # ── Module 3: RAG ────────────────────────────────────────────────────── + # Concept anchors: chunking-strategy, embedding-models, vector-search, + # hybrid-retrieval, reranking, document-ingestion, late-interaction + # Teaches: the full retrieval-augmented generation pipeline - how + # documents become chunks, how chunks become vectors, how retrieval + # and reranking find the right context, and how ingestion quality + # determines answer quality. + - id: rag + title: RAG objective: > - Ground your LLM in real data - chunking, retrieval, reranking, and citations that work. + Build the retrieval-augmented generation pipeline end to end - + document ingestion and chunking, vector and hybrid search, cross-encoder + reranking, and why ingestion quality determines answer quality. color: "#eb5757" - sort_order: 4 + sort_order: 3 + concepts: + - concept_id: chunking + sort_order: 1 + - concept_id: embeddings + sort_order: 2 + - concept_id: vector-search + sort_order: 3 + - concept_id: hybrid-search + sort_order: 4 + - concept_id: reranking + sort_order: 5 + - concept_id: retrieval-grounding + sort_order: 6 resources: - name: LlamaIndex url: https://github.com/run-llama/llama_index @@ -173,33 +179,33 @@ modules: detail: > The most complete RAG toolkit. Study the node parser, retriever, and response synthesizer abstractions. sort_order: 1 + - name: Unstructured.io + url: https://github.com/Unstructured-IO/unstructured + description: > + Document parsing and chunking for PDFs, HTML, images, and office files with layout-aware extraction. + detail: > + The ingestion layer most RAG tutorials skip. Study table extraction, section detection, and chunk boundary quality. + sort_order: 2 - name: Pinecone url: https://www.pinecone.io description: > Managed vector database with hybrid search, metadata filtering, and serverless scaling. detail: > Production vector DB for teams that want zero ops. Compare cost/latency vs self-hosted alternatives. - sort_order: 2 + sort_order: 3 - name: Weaviate url: https://weaviate.io description: > Open-source vector database with hybrid search, multi-tenancy, and built-in vectorization modules. detail: > Best open-source option for hybrid (vector + keyword) search. Study the GraphQL API and module system. - sort_order: 3 + sort_order: 4 - name: Cohere Rerank url: https://cohere.com/rerank description: > Cross-encoder reranking model that dramatically improves retrieval precision over bi-encoder search. detail: > Always add a reranking step. The lift from retrieve-then-rerank vs retrieve-only is significant. - sort_order: 4 - - name: Unstructured.io - url: https://github.com/Unstructured-IO/unstructured - description: > - Document parsing and chunking for PDFs, HTML, images, and office files with layout-aware extraction. - detail: > - The ingestion layer most RAG tutorials skip. Study table extraction, section detection, and chunk boundary quality. sort_order: 5 - name: ColBERT / RAGatouille url: https://github.com/bclavie/RAGatouille @@ -209,12 +215,94 @@ modules: Study the late-interaction pattern: why per-token matching outperforms single-vector similarity in many domains. sort_order: 6 - - id: memory - title: Memory & Personalization + # ── Module 4: Agents & Orchestration ─────────────────────────────────── + # Concept anchors: agent-loop, state-machine, multi-agent, handoff, + # human-in-the-loop, graph-orchestration, structured-output + # Teaches: how to build agents that reliably plan, execute, and recover - + # graph-based orchestration patterns, multi-agent coordination, handoff + # protocols, and why type safety matters for agent outputs. + - id: agents_orchestration + title: Agents & Orchestration objective: > - Give your AI a memory that persists across sessions and scales with usage. + Build agents that reliably plan, execute, and recover - graph-based + orchestration with cycles and persistence, multi-agent coordination + patterns, handoff protocols, and type-safe structured outputs. + color: "#4ade80" + sort_order: 4 + concepts: + - concept_id: agent-loop + sort_order: 1 + - concept_id: workflow-orchestration + sort_order: 2 + - concept_id: multi-agent-coordination + sort_order: 3 + - concept_id: handoffs + sort_order: 4 + - concept_id: checkpointing + sort_order: 5 + resources: + - name: LangGraph + url: https://github.com/langchain-ai/langgraph + description: > + Graph-based agent orchestration with cycles, persistence, and human-in-the-loop patterns. + detail: > + The most mature agent framework. Study the state machine pattern for reliable multi-step workflows. + sort_order: 1 + - name: OpenAI Agents SDK + url: https://github.com/openai/openai-agents-python + description: > + Official agent framework with handoffs, guardrails, tracing, and multi-agent orchestration. + detail: > + Study the handoff pattern for agent-to-agent delegation and the built-in tracing for debugging. + sort_order: 2 + - name: Pydantic AI + url: https://ai.pydantic.dev + description: > + Type-safe agent framework with structured outputs, dependency injection, and model-agnostic design. + detail: > + Best for Python-heavy teams wanting type safety. The structured output + validation pattern is production-grade. + sort_order: 3 + - name: CrewAI + url: https://github.com/crewAIInc/crewAI + description: > + Multi-agent framework with role-based agents, task delegation, and sequential/parallel execution. + detail: > + Study the role-play pattern for multi-agent systems. Compare vs LangGraph's explicit graph approach. + sort_order: 4 + - name: AutoGen (Microsoft) + url: https://github.com/microsoft/autogen + description: > + Multi-agent conversation framework with customizable agents, code execution, and group chat patterns. + detail: > + Study the conversational agent pattern and how it handles tool use, code generation, and verification. + sort_order: 5 + + # ── Module 5: Memory & State ─────────────────────────────────────────── + # Concept anchors: conversation-memory, tiered-memory, memory-extraction, + # entity-tracking, knowledge-graph-memory, memory-ux + # Teaches: how AI applications maintain context across sessions - memory + # extraction pipelines, tiered storage (core/archival/recall), entity + # tracking, graph-based memory, and how to surface memory to users. + - id: memory_state + title: Memory & State + objective: > + Give AI applications persistent, queryable memory - extraction + pipelines that turn conversations into structured entries, tiered + storage for short and long-term recall, entity tracking, and how + to surface memory to users without overwhelming them. color: "#5e6ad2" sort_order: 5 + concepts: + - concept_id: memory-extraction + sort_order: 1 + - concept_id: episodic-vs-semantic-memory + sort_order: 2 + - concept_id: entity-memory + sort_order: 3 + - concept_id: memory-write-policy + sort_order: 4 + - concept_id: retrieval-memory + sort_order: 5 resources: - name: Mem0 url: https://github.com/mem0ai/mem0 @@ -259,152 +347,157 @@ modules: Study the UX patterns: how to surface memory to users without being creepy or overwhelming. sort_order: 6 - - id: fine_tuning - title: Fine-Tuning & Alignment + # ── Module 6: Tool Use & Actions ─────────────────────────────────────── + # Concept anchors: function-calling, tool-schema, parallel-tool-use, + # streaming-tool-results, managed-tool-runtime, model-context-protocol + # Teaches: how LLMs interact with external systems through tools - + # function calling protocols and schema design, parallel and streaming + # execution, managed vs self-hosted tool runtimes, and the emerging + # open standard for tool/resource sharing. + - id: tool_use + title: Tool Use & Actions objective: > - Make a foundation model yours - LoRA, RLHF, and alignment without breaking the bank. + Connect LLMs to external systems through tools - function calling + protocols, tool schema design, parallel and streaming execution + patterns, managed tool runtimes, and the emerging Model Context + Protocol for standardized tool sharing. color: "#f472b6" sort_order: 6 + concepts: + - concept_id: function-calling + sort_order: 1 + - concept_id: tool-schema-design + sort_order: 2 + - concept_id: parallel-tool-use + sort_order: 3 + - concept_id: streaming-tool-results + sort_order: 4 + - concept_id: managed-tool-runtime + sort_order: 5 resources: - - name: Hugging Face PEFT - url: https://github.com/huggingface/peft + - name: Anthropic Claude Tool Use + url: https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview description: > - Parameter-efficient fine-tuning with LoRA, QLoRA, prefix tuning, and adapter methods. + Native function calling with structured outputs, parallel tool use, and streaming tool results. detail: > - Start here for any fine-tuning task. LoRA + 4-bit quantization (QLoRA) runs on a single consumer GPU. + Master the tool-use protocol: schema definition, forced tool calls, and error handling patterns. sort_order: 1 - - name: Axolotl - url: https://github.com/axolotl-ai-cloud/axolotl + - name: OpenAI Assistants API + url: https://platform.openai.com/docs/assistants/overview description: > - Streamlined fine-tuning toolkit with YAML configs for LoRA, full fine-tune, DPO, and multi-GPU training. + Managed agent infrastructure: threads, runs, tools, file search, and code interpreter. detail: > - Best developer experience for fine-tuning. One YAML config covers model, dataset, and training params. + Understand what managed tool runtimes abstract vs what you build yourself. Study the thread/run lifecycle. sort_order: 2 - - name: Unsloth - url: https://github.com/unslothai/unsloth + - name: Model Context Protocol (MCP) + url: https://modelcontextprotocol.io description: > - 2-5x faster fine-tuning with 70% less memory via custom CUDA kernels and optimized backpropagation. + Open standard for connecting LLMs to tools, data sources, and resources across any client or server. detail: > - Drop-in speedup for QLoRA training. Study the memory optimizations to understand the efficiency gains. + The emerging interop layer for tool use. Study how MCP decouples tool providers from LLM clients. sort_order: 3 - - name: TRL (Transformer Reinforcement Learning) - url: https://github.com/huggingface/trl - description: > - RLHF, DPO, PPO, and reward modeling library from Hugging Face for alignment training. - detail: > - The standard library for alignment. Study DPO vs PPO: when direct preference optimization beats full RLHF. - sort_order: 4 - - name: OpenAI Fine-Tuning - url: https://platform.openai.com/docs/guides/fine-tuning - description: > - API-based fine-tuning for GPT-4o and GPT-4o-mini with automatic hyperparameter selection. - detail: > - Easiest path to fine-tuning. Compare API fine-tuning cost/quality vs self-hosted LoRA on open models. - sort_order: 5 - - name: Anthropic Constitutional AI - url: https://www.anthropic.com/research/constitutional-ai-harmlessness-from-ai-feedback - description: > - Self-supervised alignment where the model critiques and revises its own outputs against principles. - detail: > - Study the principle-based alignment approach: how to define behavioral constraints without human labeling. - sort_order: 6 - - id: multimodal - title: Multimodal Systems + # ── Module 7: Evals & Observability ──────────────────────────────────── + # Concept anchors: eval-driven-development, rag-metrics, llm-tracing, + # embedding-drift, experiment-tracking, scoring-pipeline + # Teaches: how to know when your LLM system is broken - eval-driven + # development loops, RAG-specific quality metrics, distributed trace + # visualization for multi-step flows, and embedding drift detection. + - id: evals_observability + title: Evals & Observability objective: > - Go beyond text - images, audio, video, and cross-modal reasoning in one system. - color: "#4ade80" + Know when your LLM system is broken before your users do - eval-driven + development with scoring pipelines, RAG-specific quality metrics + (faithfulness, relevancy, recall), trace visualization for debugging + multi-step flows, and embedding drift detection. + color: "#5bb86e" sort_order: 7 + concepts: + - concept_id: eval-driven-development + sort_order: 1 + - concept_id: rag-metrics + sort_order: 2 + - concept_id: llm-tracing + sort_order: 3 + - concept_id: embedding-drift + sort_order: 4 + - concept_id: scoring-pipeline + sort_order: 5 resources: - - name: GPT-4o - url: https://openai.com/index/hello-gpt-4o/ + - name: Braintrust + url: https://www.braintrust.dev description: > - Natively multimodal model processing text, images, and audio in a unified architecture. + End-to-end LLM eval platform with scoring, experiments, datasets, and production logging. detail: > - The production standard for multimodal. Study how unified tokenization enables cross-modal reasoning. + Study the eval-driven development loop: define metrics, run experiments, compare prompts/models systematically. sort_order: 1 - - name: Google Gemini - url: https://deepmind.google/technologies/gemini/ + - name: RAGAS + url: https://github.com/explodinggradients/ragas description: > - Natively multimodal with long-context (1M+ tokens), video understanding, and code generation. + RAG evaluation framework measuring faithfulness, answer relevancy, context precision, and recall. detail: > - Study the long-context multimodal pattern: processing entire videos and documents in a single call. + Essential for any RAG system. Implement the four core metrics as CI gates for retrieval quality. sort_order: 2 - - name: LLaVA - url: https://llava-vl.github.io + - name: LangSmith + url: https://smith.langchain.com description: > - Open-source vision-language model connecting CLIP visual encoder to LLaMA language model. + Tracing, evaluation, and monitoring platform for LLM applications with dataset management. detail: > - The reference architecture for open VLMs. Study the visual instruction tuning dataset and training pipeline. + Use for tracing complex chains/agents. The trace visualization is invaluable for debugging multi-step flows. sort_order: 3 - - name: OpenAI Whisper - url: https://github.com/openai/whisper + - name: Arize Phoenix + url: https://github.com/Arize-ai/phoenix description: > - Robust speech recognition across languages, accents, and noise conditions with zero-shot generalization. + Open-source LLM observability with tracing, evals, embeddings analysis, and retrieval diagnostics. detail: > - Production-grade ASR. Study the encoder-decoder architecture and how it handles multilingual transcription. + Best open-source option. Study the embedding drift detection and retrieval quality metrics. sort_order: 4 - - name: Twelve Labs - url: https://www.twelvelabs.io - description: > - Video understanding API with temporal search, summarization, and generation from video content. - detail: > - Study how video embeddings differ from image embeddings: temporal awareness and action recognition. - sort_order: 5 - - name: Pixtral / Qwen-VL - url: https://huggingface.co/Qwen/Qwen2.5-VL-72B-Instruct - description: > - Open-weight vision-language models approaching GPT-4o quality on visual reasoning benchmarks. - detail: > - Track the open VLM frontier. Compare Qwen-VL, Pixtral, and InternVL on your domain-specific tasks. - sort_order: 6 - - id: reasoning - title: Reasoning & Planning + # ── Module 8: Production Patterns ────────────────────────────────────── + # Concept anchors: gpu-optimized-serving, constrained-generation, + # io-validation, safety-layer, structured-output-enforcement + # Teaches: the last mile of deploying AI applications - GPU-optimized + # serving for production throughput, constrained/structured output + # enforcement, and input/output validation as a safety layer. + - id: production_patterns + title: Production Patterns objective: > - Unlock multi-step reasoning - chain-of-thought, tree search, and self-consistency. - color: "#55cdff" + Deploy AI applications that stay reliable under load - GPU-optimized + serving for production throughput, constrained and structured output + enforcement, and input/output validation to catch hallucinations, + PII leaks, and schema violations before they reach users. + color: "#eb5757" sort_order: 8 + concepts: + - concept_id: gpu-optimized-serving + sort_order: 1 + - concept_id: constrained-generation + sort_order: 2 + - concept_id: structured-output-enforcement + sort_order: 3 + - concept_id: io-validation + sort_order: 4 + - concept_id: safety-layer + sort_order: 5 resources: - - name: OpenAI o1 / o3 - url: https://openai.com/index/learning-to-reason-with-llms/ + - name: NVIDIA TensorRT-LLM + url: https://github.com/NVIDIA/TensorRT-LLM description: > - Chain-of-thought reasoning models with internal deliberation for math, science, and coding. + NVIDIA's optimized inference library with FP8 quantization, in-flight batching, and multi-GPU tensor parallelism. detail: > - Study the test-time compute scaling paradigm: spending more inference tokens improves reasoning quality. + Best raw performance on NVIDIA hardware. Study when GPU optimization matters vs when vLLM is good enough. sort_order: 1 - - name: Claude Extended Thinking - url: https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking + - name: SGLang + url: https://github.com/sgl-project/sglang description: > - Explicit thinking traces in Claude for complex analysis, planning, and multi-step problem solving. + Fast structured generation with RadixAttention for prefix caching and constrained decoding. detail: > - Study how exposing the reasoning chain improves transparency and enables better prompt engineering. + Study RadixAttention for KV-cache sharing across requests. Use for enforcing JSON/schema-constrained output in production. sort_order: 2 - - name: DeepSeek-R1 - url: https://github.com/deepseek-ai/DeepSeek-R1 + - name: Guardrails AI + url: https://github.com/guardrails-ai/guardrails description: > - Open-weight reasoning model trained with RL to generate detailed chain-of-thought before answering. + Input/output validation framework with validators for PII, toxicity, hallucination, and schema compliance. detail: > - The open-source reasoning frontier. Study the RL training recipe: how reinforcement learning teaches reasoning. + Production safety layer. Study the validator composability and how to build custom domain validators. sort_order: 3 - - name: Tree of Thoughts - url: https://github.com/princeton-nlp/tree-of-thought-llm - description: > - Deliberate problem solving via tree search over reasoning paths with evaluation and backtracking. - detail: > - Study the search-over-thoughts pattern: BFS/DFS over reasoning branches with LLM-as-evaluator. - sort_order: 4 - - name: Self-Consistency (Wang et al.) - url: https://arxiv.org/abs/2203.11171 - description: > - Sample multiple reasoning paths and take majority vote. Simple technique that reliably boosts accuracy. - detail: > - The cheapest reasoning improvement: sample N completions, majority-vote the answer. Works with any model. - sort_order: 5 - - name: LATS (Language Agent Tree Search) - url: https://github.com/laetitia-teo/lats - description: > - Monte Carlo tree search for LLM agents combining reasoning, acting, and planning with environment feedback. - detail: > - Study MCTS for agents: how tree search + LLM evaluation enables planning in complex environments. - sort_order: 6 diff --git a/curriculum/tracks/backend-systems.yaml b/curriculum/tracks/backend-systems.yaml new file mode 100644 index 00000000..8914d374 --- /dev/null +++ b/curriculum/tracks/backend-systems.yaml @@ -0,0 +1,324 @@ +id: backend-systems +title: Backend & Distributed Systems +description: > + How backend systems communicate, distribute work, handle failure, and run in production. + Progression: real-time communication, service interfaces, distributed theory, + event-driven patterns, containerized deployment, operational hardening. +difficulty: intermediate +track_type: resource +category: systems +category_order: 2 +track_order: 4 +modules: + # ── Module 1: Real-Time Systems ────────────────────────────────────── + # Concept anchors: realtime-vs-request, pub-sub, backpressure, + # stateful-connections, push-vs-poll + # Teaches: why some systems need persistent connections instead of + # request/response, how push models work, and where stateful edge + # compute fits. + - id: realtime_systems + title: Real-Time Systems + objective: > + Understand when and why systems need persistent connections instead of + request/response - push vs polling, pub/sub fan-out, backpressure, + and stateful session coordination. + color: "#55cdff" + sort_order: 1 + concepts: + - concept_id: realtime-vs-request + sort_order: 1 + - concept_id: push-vs-poll + sort_order: 2 + - concept_id: pub-sub + sort_order: 3 + - concept_id: backpressure + sort_order: 4 + - concept_id: stateful-connections + sort_order: 5 + resources: + - name: MDN WebSocket API + url: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API + description: Core browser/server bidirectional transport for live UX, streaming updates, and control messages. + detail: The foundation. Know heartbeats, reconnect strategy, auth refresh, and backpressure handling before reaching for frameworks. + sort_order: 1 + - name: Ably + url: https://ably.com/docs + description: Managed pub/sub and WebSocket infrastructure with global edge fan-out and delivery guarantees. + detail: Study the pub/sub model and delivery guarantees. Good mental model for when to use managed real-time vs rolling your own. + sort_order: 2 + - name: LiveKit + url: https://livekit.io + description: Real-time transport infrastructure for audio, video, and data channels used in interactive AI products. + detail: Where real-time gets hard - media transport with strict latency budgets. Study the session model and selective forwarding. + sort_order: 3 + - name: NATS + url: https://docs.nats.io + description: Lightweight messaging for low-latency request/reply, fan-out, and control plane signaling. + detail: The simplest real-time messaging system. Study request/reply, pub/sub, and JetStream for persistence. Contrast with heavier brokers. + sort_order: 4 + - name: Cloudflare Durable Objects + url: https://developers.cloudflare.com/durable-objects/ + description: Stateful edge compute for room/session coordination and shared low-latency state. + detail: Teaches the stateful connection pattern - when you need per-session state at the edge rather than stateless request routing. + sort_order: 5 + + # ── Module 2: APIs at Scale ────────────────────────────────────────── + # Concept anchors: api-design-patterns, rest-vs-rpc, api-gateway, + # internal-vs-external-api, protocol-selection + # Teaches: how to design service interfaces, when to use REST vs RPC, + # what sits between clients and services, and how framework choice + # shapes API design. + - id: apis_at_scale + title: APIs at Scale + objective: > + Learn how services expose interfaces to each other and to clients - + REST vs RPC tradeoffs, gateway patterns for cross-cutting concerns, + and how to choose between frameworks and protocols for internal vs + external APIs. + color: "#ffc47c" + sort_order: 2 + concepts: + - concept_id: rest-vs-rpc + sort_order: 1 + - concept_id: api-gateway + sort_order: 2 + - concept_id: internal-vs-external-api + sort_order: 3 + - concept_id: protocol-selection + sort_order: 4 + - concept_id: api-design-patterns + sort_order: 5 + resources: + - name: Kong Gateway + url: https://docs.konghq.com + description: "API gateway: rate limiting, auth, load balancing, observability plugins, and service mesh integration." + detail: Study the gateway pattern itself, not just Kong. Understand what cross-cutting concerns belong at the gateway vs in the service. + sort_order: 1 + - name: gRPC Documentation + url: https://grpc.io/docs/ + description: "High-performance RPC framework: protobuf, streaming, deadlines, and service-to-service communication." + detail: The default for internal service-to-service APIs. Study protobuf schema evolution, streaming modes, and deadline propagation. + sort_order: 2 + - name: FastAPI + url: https://fastapi.tiangolo.com + description: "Modern Python API framework: async, type hints, auto-docs, WebSocket support, and dependency injection." + detail: Best Python framework for ML/AI service APIs. Study the middleware stack, dependency injection, and how type hints drive docs. + sort_order: 3 + - name: Hono + url: https://hono.dev + description: "Ultra-fast edge-first web framework: works on Cloudflare Workers, Deno, Bun, and Node. TypeScript-native." + detail: Represents the edge-first API pattern. Study how middleware composition and deployment target shape API design differently than server frameworks. + sort_order: 4 + + # ── Module 3: Distributed Systems Fundamentals ─────────────────────── + # Concept anchors: replication, partitioning, consistency-models, + # consensus, failure-modes, cap-theorem + # Teaches: the foundational theory for any system that spans multiple + # machines - what can go wrong, what tradeoffs are unavoidable, and + # how consensus works. + - id: distributed_systems + title: Distributed Systems Fundamentals + objective: > + Build the mental models for systems that span multiple machines - + replication strategies, partitioning schemes, consistency models, + consensus protocols, and what failure modes are unavoidable. + color: "#5bb86e" + sort_order: 3 + concepts: + - concept_id: replication + sort_order: 1 + - concept_id: partitioning + sort_order: 2 + - concept_id: consistency-models + sort_order: 3 + - concept_id: consensus + sort_order: 4 + - concept_id: failure-modes + sort_order: 5 + resources: + - name: Designing Data-Intensive Applications + url: https://dataintensive.net + description: "The bible for distributed systems: replication, partitioning, consistency, and batch/stream processing." + detail: Read chapters 5-9 for the core mental models. This is theory that applies to every system you will ever build or debug. + sort_order: 1 + - name: "Distributed Systems (Maarten van Steen & Andrew Tanenbaum)" + url: https://www.distributed-systems.net/index.php/books/ds4/ + description: Free textbook covering communication, naming, synchronization, consistency, replication, and fault tolerance. + detail: More academic than DDIA. Deepens understanding of consensus algorithms, logical clocks, and Byzantine failure models. + sort_order: 2 + - name: "The Raft Consensus Algorithm" + url: https://raft.github.io + description: Understandable consensus protocol with leader election, log replication, and safety proofs. + detail: Raft is the foundation of etcd, CockroachDB, and TiKV. Understand leader election and log commitment before studying Paxos. + sort_order: 3 + - name: Jepsen Analyses + url: https://jepsen.io/analyses + description: Rigorous consistency and partition testing of real distributed databases and queues. + detail: Study the methodology - how Kyle Kingsbury breaks systems reveals what consistency guarantees actually mean vs what vendors claim. + sort_order: 4 + - name: microservices.io Patterns + url: https://microservices.io/patterns/index.html + description: "Pattern catalog for distributed service architectures: saga, CQRS, circuit breaker, service mesh, API gateway." + detail: Reference catalog for when you need a specific distributed pattern. Not a tutorial - use after you have the theory from DDIA. + sort_order: 5 + + # ── Module 4: Event-Driven Architecture ────────────────────────────── + # Concept anchors: event-driven-vs-request, event-replay, idempotency, + # queue-vs-log, eventual-consistency, event-sourcing + # Teaches: the paradigm shift from request/response to event-based + # systems - when events beat requests, how logs differ from queues, + # and why idempotent consumers matter. + - id: event_driven + title: Event-Driven Architecture + objective: > + Understand the paradigm shift from request/response to event-based + systems - when events beat synchronous calls, how durable logs differ + from transient queues, why replay and idempotent consumers are + non-negotiable, and how eventual consistency changes system design. + color: "#5e6ad2" + sort_order: 4 + concepts: + - concept_id: event-driven-vs-request + sort_order: 1 + - concept_id: queue-vs-log + sort_order: 2 + - concept_id: event-replay + sort_order: 3 + - concept_id: idempotency + sort_order: 4 + - concept_id: eventual-consistency + sort_order: 5 + - concept_id: event-sourcing + sort_order: 6 + resources: + - name: "Martin Fowler - Event-Driven Architecture" + url: https://martinfowler.com/articles/201701-event-driven.html + description: Clarifies the four meanings of event-driven - event notification, event-carried state, event sourcing, and CQRS. + detail: Read this first. Most confusion about event-driven systems comes from conflating these four distinct patterns. + sort_order: 1 + - name: Apache Kafka + url: https://kafka.apache.org/documentation/ + description: Durable event streaming for high-throughput pipelines, replayable logs, and consumer groups. + detail: The dominant event streaming platform. Study partitioning, consumer groups, exactly-once semantics, and compacted topics. + sort_order: 2 + - name: RabbitMQ Tutorials + url: https://www.rabbitmq.com/tutorials + description: "Classic message broker: queues, exchanges, routing, acknowledgments, and dead-letter handling." + detail: "Teaches the queue model. RabbitMQ = work distribution and routing. Kafka = replayable event logs. Know when each fits." + sort_order: 3 + - name: "Event Sourcing (Greg Young)" + url: https://www.eventstore.com/event-sourcing + description: Store events, not state. Derive current state by replaying the event log. + detail: The strongest form of event-driven design. Know the tradeoffs - full audit trail vs query complexity and storage growth. + sort_order: 4 + - name: CloudEvents Spec + url: https://cloudevents.io + description: Vendor-neutral specification for describing event data in a common format across systems. + detail: The emerging standard for event envelopes. Study when standardized event schemas matter vs when they add overhead. + sort_order: 5 + + # ── Module 5: Containers & Orchestration ───────────────────────────── + # Concept anchors: containerization, orchestration, scheduling, + # service-discovery, scaling-primitives + # Teaches: how modern services are packaged, deployed, and scaled - + # the abstraction layer between "code that runs" and "infrastructure + # that hosts it." + - id: containers_orchestration + title: Containers & Orchestration + objective: > + Learn the abstraction layer between your code and infrastructure - + how containers package services for portability, how orchestrators + schedule and scale them, and how service discovery and config + management work in container-native environments. + color: "#f472b6" + sort_order: 5 + concepts: + - concept_id: containerization + sort_order: 1 + - concept_id: orchestration + sort_order: 2 + - concept_id: scheduling + sort_order: 3 + - concept_id: service-discovery + sort_order: 4 + - concept_id: scaling-primitives + sort_order: 5 + resources: + - name: Docker Documentation + url: https://docs.docker.com + description: "Container fundamentals: images, layers, multi-stage builds, networking, volumes, and Compose for local dev." + detail: Master multi-stage builds and layer caching first. Then learn Compose for local multi-service development. + sort_order: 1 + - name: Kubernetes Documentation + url: https://kubernetes.io/docs/home/ + description: "Container orchestration: pods, deployments, services, ingress, ConfigMaps, and horizontal autoscaling." + detail: Start with pods, deployments, and services. These three primitives cover 80% of what you need. Skip operators and CRDs until you need them. + sort_order: 2 + - name: "Kubernetes the Hard Way (Kelsey Hightower)" + url: https://github.com/kelseyhightower/kubernetes-the-hard-way + description: Bootstrap a Kubernetes cluster from scratch to understand every component and how they connect. + detail: Do this once to understand what managed K8s abstracts away. Builds the mental model for debugging orchestration issues. + sort_order: 3 + - name: Helm Charts + url: https://helm.sh/docs/ + description: "Kubernetes package manager: templated manifests, release management, and chart repositories." + detail: The standard way to package and distribute Kubernetes applications. Study values.yaml overrides and chart dependencies. + sort_order: 4 + - name: Buildpacks (Cloud Native) + url: https://buildpacks.io + description: Auto-detect language and produce OCI images without writing Dockerfiles. Used by Heroku, GCP, and Railway. + detail: The alternative to Dockerfiles for standardized builds. Study when convention-over-configuration beats explicit container definitions. + sort_order: 5 + + # ── Module 6: Production Hardening ─────────────────────────────────── + # Concept anchors: observability, load-testing, slo-error-budget, + # incident-response, deployment-strategy + # Teaches: how to know your system is healthy, how to prove it can + # handle load, how to respond when it breaks, and how to deploy + # without breaking it. + - id: production_hardening + title: Production Hardening + objective: > + Learn how to know your system is healthy, prove it can handle load, + respond when it breaks, and deploy without causing incidents - + observability instrumentation, load testing methodology, SLO-based + reliability, and incident response patterns. + color: "#eb5757" + sort_order: 6 + concepts: + - concept_id: observability + sort_order: 1 + - concept_id: load-testing + sort_order: 2 + - concept_id: slo-error-budget + sort_order: 3 + - concept_id: incident-response + sort_order: 4 + - concept_id: deployment-strategy + sort_order: 5 + resources: + - name: OpenTelemetry + url: https://opentelemetry.io/docs/ + description: "Vendor-neutral telemetry standard: traces, metrics, and logs with auto-instrumentation for major frameworks." + detail: Start here. OTel is the instrumentation standard. Learn to add traces and metrics before choosing a backend to send them to. + sort_order: 1 + - name: Grafana + Prometheus Stack + url: https://grafana.com/docs/grafana/latest/ + description: "Open-source observability backend: metrics (Prometheus), logs (Loki), traces (Tempo), and dashboards (Grafana)." + detail: The default observability backend. Study after OTel - this is where your instrumentation data goes and how you visualize it. + sort_order: 2 + - name: Google SRE Books + url: https://sre.google/books/ + description: "Site Reliability Engineering canon: SLOs, error budgets, toil reduction, incident response, and on-call practices." + detail: Read chapters on SLOs and error budgets first. These frameworks define when to invest in reliability vs ship features. + sort_order: 3 + - name: k6 Load Testing + url: https://k6.io/docs/ + description: "Modern load testing: scripted scenarios, thresholds, CI integration, and distributed testing." + detail: Know how to load test before launch. Practice writing k6 scripts with realistic traffic patterns and pass/fail thresholds. + sort_order: 4 + - name: Fly.io / Railway + url: https://fly.io/docs/ + description: "Edge deployment platforms: containers, global distribution, auto-scaling, and production Postgres." + detail: Modern deployment platforms that make production deployment tangible. Study zero-downtime deploys and global distribution. + sort_order: 5 diff --git a/curriculum/tracks/behavioral-design.yaml b/curriculum/tracks/behavioral-design.yaml deleted file mode 100644 index c99244c4..00000000 --- a/curriculum/tracks/behavioral-design.yaml +++ /dev/null @@ -1,320 +0,0 @@ -id: behavioral-design -title: Behavioral Design -description: > - Persuasion frameworks, engagement loops, and product psychology patterns. -difficulty: intermediate -track_type: resource -category: product -category_order: 3 -track_order: 1 -modules: - - id: frameworks - title: Frameworks - objective: > - The psychology models behind why people stay, buy, and come back. - sort_order: 1 - resources: - - name: "Fogg Behavior Model (B=MAP) - BJ Fogg" - description: "You don't need to increase motivation if you make the behavior easy enough and trigger it at the right time." - detail: "One-tap likes, frictionless sharing, push notifications timed to high-motivation moments (loneliness, boredom)." - resource_type: knowledge - sort_order: 1 - - name: "Hook Model - Nir Eyal" - description: "Variable rewards create compulsive checking. The investment phase (profile building, follower accumulation) increases switching costs." - detail: "Instagram notification -> open app -> scroll feed (variable content) -> post/comment (investment in social graph)." - resource_type: knowledge - sort_order: 2 - - name: "Operant Conditioning - B.F. Skinner" - description: "Slot machines and social feeds use identical reward schedules. Unpredictable rewards are more addictive than predictable ones." - detail: "Pull-to-refresh (slot machine lever), algorithmic feed randomization, unpredictable like counts." - resource_type: knowledge - sort_order: 3 - - name: "Dual Process Theory - Daniel Kahneman" - description: "Design for System 1 and users will act before they think. Infinite scroll, autoplay, and one-click actions all bypass deliberation." - detail: "Autoplay next episode, infinite scroll with no stopping cue, swipe-based interactions (Tinder, TikTok)." - resource_type: knowledge - sort_order: 4 - - name: "Six Principles of Persuasion - Robert Cialdini" - description: "Social proof (like counts, view counts) and scarcity (limited-time offers, disappearing content) are the most weaponized in tech." - detail: "\"X people are viewing this\" (Booking.com), disappearing stories (Snapchat), follower counts as authority signals." - resource_type: knowledge - sort_order: 5 - - - id: feed_design - title: "Feed & Content" - objective: > - How feeds keep you scrolling - ranking, sequencing, and content packaging tactics. - sort_order: 2 - resources: - - name: Infinite Scroll - description: "Content loads endlessly with no natural stopping point, eliminating the pause that would trigger a conscious decision to stop." - detail: "Removes completion cues. The brain never receives a 'finished' signal, so the default behavior is to keep going." - resource_type: knowledge - sort_order: 1 - - name: Autoplay - description: "Next video/episode starts automatically. Stopping requires active effort; continuing is passive." - detail: "Exploits status quo bias and loss aversion. Opting out feels like losing something vs. doing nothing." - resource_type: knowledge - sort_order: 2 - - name: Pull-to-Refresh - description: "Physical gesture that mimics a slot machine lever pull, creating a tactile reward loop." - detail: "Variable-ratio reinforcement. Each pull might reveal new content (reward) or not, creating compulsive repetition." - resource_type: knowledge - sort_order: 3 - - name: Algorithmic Feed - description: "Content ordered by engagement prediction rather than chronology. Shows you what maximizes time-on-app, not what's newest." - detail: "Collaborative filtering + reinforcement learning. Optimizes for engagement proxies (clicks, dwell time, shares) which correlate with emotional arousal." - resource_type: knowledge - sort_order: 4 - - name: Short-Form Vertical Video - description: "Bite-sized content in full-screen vertical format that requires minimal cognitive investment per unit." - detail: "Low effort per item + variable reward per swipe = highest dopamine-per-minute of any content format." - resource_type: knowledge - sort_order: 5 - - name: Sound-On Default - description: "Auto-playing audio captures attention involuntarily and increases emotional engagement with content." - detail: "Audio is processed pre-attentively - you react to sound before you consciously decide to. Increases dwell time significantly." - resource_type: knowledge - sort_order: 6 - - name: Clickbait / Curiosity Gap - description: "Headlines that open an information gap without closing it, creating an irresistible urge to click." - detail: "Loewenstein's information gap theory: the brain treats an open question as an unresolved tension that must be closed." - resource_type: knowledge - sort_order: 7 - - name: Engagement Bait Algorithms - description: "Recommendation systems that learn individual vulnerabilities and exploit them to maximize session length." - detail: "Multi-armed bandit optimization that treats each user as an independent exploitation problem. Finds your specific triggers." - resource_type: knowledge - sort_order: 8 - - - id: social_loops - title: Social Loops - objective: > - Why you keep coming back - identity, reciprocity, and social pressure loops. - sort_order: 3 - resources: - - name: Like Counts / Reactions - description: "Public quantification of social approval that creates a dopamine feedback loop for both poster and audience." - detail: "Social comparison theory + variable reward. Each check might show new likes (or not), creating compulsive monitoring." - resource_type: knowledge - sort_order: 1 - - name: View / Play Counts - description: "Public metrics that create social proof and drive content creators into optimization loops." - detail: "Social proof (Cialdini) - high view counts signal value, creating a winner-take-all attention economy." - resource_type: knowledge - sort_order: 2 - - name: Read Receipts / Typing Indicators - description: "Showing when someone has seen your message creates social obligation to respond immediately." - detail: "Exploits reciprocity norm and social anxiety. Leaving a message 'on read' feels like a social violation." - resource_type: knowledge - sort_order: 3 - - name: Streaks - description: "Consecutive-day usage counters that create artificial commitment through loss aversion." - detail: "Loss aversion is 2x stronger than equivalent gains. A 100-day streak feels too valuable to break, even if the activity itself has no value." - resource_type: knowledge - sort_order: 4 - - name: Follower / Friend Counts - description: "Public social capital metrics that create status hierarchies and competitive accumulation." - detail: "Status games + quantified social comparison. The number becomes the goal, detached from actual relationship quality." - resource_type: knowledge - sort_order: 5 - - name: Social Reciprocity Triggers - description: "Notifications that someone interacted with you, creating obligation to interact back." - detail: "Cialdini's reciprocity principle: receiving creates a felt obligation to give back. Platforms engineer mutual obligations." - resource_type: knowledge - sort_order: 6 - - - id: variable_rewards - title: Variable Rewards - objective: > - The slot machine in every app - unpredictable rewards that build habits. - sort_order: 4 - resources: - - name: Loot Box / Gacha Mechanics - description: "Randomized rewards with varying rarity that create gambling-like compulsion loops." - detail: "Variable-ratio reinforcement (Skinner). The unpredictability of reward magnitude drives compulsive repetition. Identical to slot machines." - resource_type: knowledge - sort_order: 1 - - name: Disappearing Content (Stories) - description: "Content that expires after 24 hours, creating urgency and FOMO that drives frequent checking." - detail: "Scarcity principle (Cialdini) + loss aversion. Content that disappears feels more valuable. Missing it feels like loss." - resource_type: knowledge - sort_order: 2 - - name: Feed Refresh Randomization - description: "Each feed refresh shows different content, making each visit a unique 'pull' of the slot machine." - detail: "Intermittent reinforcement. Sometimes the feed is boring, sometimes it's perfect - the uncertainty is what creates compulsion." - resource_type: knowledge - sort_order: 3 - - name: Micro-Reward Variability - description: "Small, unpredictable rewards scattered throughout the experience - new followers, likes, comments arriving at random intervals." - detail: "Each notification is a mini-reward with uncertain timing and magnitude. The brain treats the phone as a reward-dispensing device." - resource_type: knowledge - sort_order: 4 - - - id: friction - title: "Friction & Dark Patterns" - objective: > - How products make it hard to leave - friction design and dark patterns. - sort_order: 5 - resources: - - name: Friction Asymmetry - description: "Signing up is one click; deleting your account requires 15 steps, phone calls, and waiting periods." - detail: "Asymmetric friction design - make desired behaviors frictionless and undesired behaviors maximally effortful." - resource_type: knowledge - sort_order: 1 - - name: Confirm-shaming - description: "Opt-out text designed to make the user feel bad about their choice. 'No, I don't want to save money.'" - detail: "Social pressure + loss framing. The opt-out is phrased as self-harm, making the user feel stupid for declining." - resource_type: knowledge - sort_order: 2 - - name: Default-On Settings - description: "Privacy-invasive, attention-capturing, or monetization features are enabled by default. Changing requires finding buried settings." - detail: "Status quo bias (Kahneman). 90%+ of users never change defaults. The default IS the choice for most people." - resource_type: knowledge - sort_order: 3 - - name: One-Click Purchase - description: "Removing deliberation time from buying decisions. The faster the purchase, the less rational evaluation occurs." - detail: "Eliminates the cooling-off period where System 2 might intervene. Impulse buying becomes the default mode." - resource_type: knowledge - sort_order: 4 - - name: Roach Motel Pattern - description: "Easy to get into, nearly impossible to get out of. Subscriptions, accounts, data exports deliberately obstructed." - detail: "Sunk cost fallacy + procedural friction. Users stay because leaving is too painful, not because the product is good." - resource_type: knowledge - sort_order: 5 - - - id: notifications - title: "Notifications & Lock-in" - objective: > - The science of pulling you back in - notification timing, triggers, and lock-in. - sort_order: 6 - resources: - - name: Push Notification Batching - description: "Strategically timing notifications to arrive when users are most likely to re-engage (morning, lunch, evening)." - detail: "Fogg's prompt timing - trigger must arrive at a high-motivation moment. ML models predict optimal send times per user." - resource_type: knowledge - sort_order: 1 - - name: FOMO Notifications - description: "'Your friend just posted for the first time in a while,' 'You have unseen memories,' 'Trending near you.'" - detail: "Fear of missing out exploits social belonging needs. Missing content feels like missing a social event." - resource_type: knowledge - sort_order: 2 - - name: Badge Counts (Red Dots) - description: "Unread counters on app icons that create visual tension demanding resolution." - detail: "Zeigarnik effect - incomplete tasks create psychological tension. The red badge is an unresolved task that demands attention." - resource_type: knowledge - sort_order: 3 - - name: Time-Delayed Notifications - description: "Holding back notifications to deliver them strategically rather than in real-time." - detail: "Creates unpredictable reward timing. User never knows when the next notification will arrive, increasing checking behavior." - resource_type: knowledge - sort_order: 4 - - name: Recommendation Engines - description: "Algorithmic content suggestions that create an endless tunnel of 'next' items to consume." - detail: "Collaborative filtering finds patterns across millions of users to predict what will keep YOU specifically engaged longest." - resource_type: knowledge - sort_order: 5 - - name: Collaborative Filtering - description: "'Users like you also watched/bought/liked...' - leveraging collective behavior to predict individual preferences." - detail: "Social proof at scale. The recommendation feels personalized but is actually a statistical prediction from aggregate behavior." - resource_type: knowledge - sort_order: 6 - - name: Taste Profiles / Filter Bubbles - description: "Increasingly narrow content personalization that traps users in comfortable echo chambers." - detail: "Confirmation bias amplification. The algorithm shows you more of what you already engage with, creating a shrinking worldview." - resource_type: knowledge - sort_order: 7 - - name: Social Graph Lock-in - description: "Your social connections become the switching cost. Leaving the platform means losing access to your network." - detail: "Network effects + sunk cost. Years of relationship building become hostage. The platform doesn't need to be good, just irreplaceable." - resource_type: knowledge - sort_order: 8 - - - id: gamification - title: "Gamification & Temporal" - objective: > - Streaks, progress bars, and time pressure - the mechanics of long-term engagement. - sort_order: 7 - resources: - - name: Progress Bars / Completion Loops - description: "Visual indicators showing how close you are to completing a profile, level, or achievement." - detail: "Goal gradient effect - effort increases as you approach a goal. An 80% complete profile feels like unfinished business." - resource_type: knowledge - sort_order: 1 - - name: Badges & Achievements - description: "Virtual rewards for platform-desired behaviors that create a collection instinct." - detail: "Operant conditioning + completionism. Each badge is a micro-reward that shapes behavior toward platform goals." - resource_type: knowledge - sort_order: 2 - - name: Leaderboards / Rankings - description: "Public competitive rankings that exploit status-seeking and social comparison." - detail: "Social comparison theory (Festinger). Rankings create winners and losers, driving both groups to increase engagement." - resource_type: knowledge - sort_order: 3 - - name: Creator Monetization - description: "Revenue sharing that turns users into content-producing employees with variable compensation." - detail: "Variable-ratio reinforcement applied to income. Viral posts create intermittent large rewards, driving compulsive content creation." - resource_type: knowledge - sort_order: 4 - - name: Hiding Time Indicators - description: "Removing or minimizing clock displays, session length indicators, and usage data from the interface." - detail: "Time blindness. Without temporal cues, users lose track of how long they've been engaged. The session extends unnoticed." - resource_type: knowledge - sort_order: 5 - - name: Removing Endpoints - description: "Eliminating natural stopping points in the content consumption experience." - detail: "Without a 'done' signal, the default is to continue. Removing pagination, episode counts, and 'end of feed' signals keeps users going." - resource_type: knowledge - sort_order: 6 - - name: Live / Ephemeral Content - description: "Real-time content (live streams, live events) that creates urgency through irreversibility." - detail: "Scarcity + FOMO. Live content can't be consumed later (or feels lesser when recorded). Missing it is permanent." - resource_type: knowledge - sort_order: 7 - - name: Limited-Time Events - description: "Seasonal events, flash sales, and time-bounded content that create artificial urgency." - detail: "Scarcity principle + loss aversion. Time pressure disables deliberate thinking and triggers impulsive action." - resource_type: knowledge - sort_order: 8 - - - id: case_studies - title: Case Studies - objective: > - How TikTok, Instagram, Duolingo, and others apply these patterns in the real world. - sort_order: 8 - resources: - - name: "Meta (Facebook/Instagram)" - description: "The social comparison machine" - detail: "News Feed Algorithm; Instagram's Social Comparison Engine; Growth Team Tactics" - resource_type: knowledge - sort_order: 1 - - name: TikTok - description: "The attention singularity" - detail: "For You Page Algorithm; Full-Screen Vertical Format; Creator Incentive Structure" - resource_type: knowledge - sort_order: 2 - - name: YouTube - description: "The radicalization pipeline" - detail: "Recommendation Algorithm; Autoplay + Up Next; Thumbnail/Title Optimization" - resource_type: knowledge - sort_order: 3 - - name: Snapchat - description: "The teen engagement machine" - detail: "Streaks; Snap Map; Disappearing Content" - resource_type: knowledge - sort_order: 4 - - name: Apple - description: "The ecosystem lock-in architect" - detail: "iMessage Blue Bubble; Ecosystem Interdependence; App Store Control" - resource_type: knowledge - sort_order: 5 - - name: Amazon - description: "The friction elimination machine" - detail: "1-Click Buy; Prime Membership; Dark Patterns in Cancellation" - resource_type: knowledge - sort_order: 6 - - name: Netflix - description: "The binge engineering pioneer" - detail: "Autoplay Everything; Personalized Artwork; Removal of Episode Counts" - resource_type: knowledge - sort_order: 7 diff --git a/curriculum/tracks/bio-augmentation.yaml b/curriculum/tracks/bio-augmentation.yaml deleted file mode 100644 index 41ec2fef..00000000 --- a/curriculum/tracks/bio-augmentation.yaml +++ /dev/null @@ -1,388 +0,0 @@ -id: bio-augmentation -title: Bio-Augmentation -description: > - Human augmentation from biology to brain-computer interfaces - foundational - biology, neurotech, wearables, biohacking, live translation, and the convergence - of human-machine fusion. -difficulty: intermediate -track_type: resource -category: specialization -category_order: 4 -track_order: 2 -modules: - - id: foundations - title: Bio Foundations - objective: > - The biology you need to understand before hacking it - cells, DNA, and the - brain. - color: "#55cdff" - sort_order: 1 - resources: - - name: MIT OpenCourseWare - Biology (7.013) - url: https://ocw.mit.edu/courses/7-013-introductory-biology-spring-2018/ - description: > - Introductory biology covering cell structure, genetics, molecular biology, - and biochemistry. - detail: > - Start here. Covers DNA replication, gene expression, protein synthesis - the - vocab you need for everything else. - sort_order: 1 - - name: Khan Academy - Biology & Genetics - url: https://www.khanacademy.org/science/biology - description: > - Visual, step-by-step biology from cells to genetics to human physiology. - detail: > - Use for gap-filling. Jump to the specific unit you need (genetics, molecular - bio, human body systems). - sort_order: 2 - - name: Neuroscience Online (UTHealth) - url: https://nba.uth.tmc.edu/neuroscience/ - description: > - Free open-access neuroscience textbook covering neurons, synapses, sensory - systems, and motor control. - detail: > - Essential for understanding BCI. Focus on chapters 1-4 (cellular - neuroscience) and 8 (somatosensory). - sort_order: 3 - - name: NHGRI Genomics Education - url: https://www.genome.gov/about-genomics - description: > - NIH's primer on genomics: DNA, genes, chromosomes, genetic variation, and - personalized medicine. - detail: > - Read the fact sheets first. Covers CRISPR, gene therapy, pharmacogenomics in - plain language. - sort_order: 4 - - name: Molecular Biology of the Cell (Alberts) - url: https://www.ncbi.nlm.nih.gov/books/NBK21054/ - description: > - The standard reference textbook for cell and molecular biology, freely - available via NCBI. - detail: > - Deep reference. Read chapters 4-7 (DNA, gene expression, proteins) when you - need real depth. - sort_order: 5 - - name: 3Blue1Brown - Biology Essentials - url: https://www.youtube.com/c/3blue1brown - description: > - Visual math and science explanations. Look for videos on information theory, - neural networks, and signal processing. - detail: > - Not biology-specific but the mathematical intuition applies directly to - biosignal processing and neural coding. - sort_order: 6 - - - id: neurotech - title: Neurotech & BCI - objective: > - Read and write to the brain directly - BCIs, neural implants, and where the - field is heading. - color: "#ffc47c" - sort_order: 2 - resources: - - name: Neuralink - url: https://neuralink.com - description: > - Implantable BCI with 1024+ electrode threads. First human trials ongoing for - paralysis patients. - detail: > - Track the N1 implant progress - electrode count, signal quality, and the - robotic insertion system. - sort_order: 1 - - name: Synchron Stentrode - url: https://synchron.com - description: > - Endovascular BCI inserted via blood vessels - no open brain surgery required. - FDA breakthrough device. - detail: > - Study the less-invasive approach: stent-based electrode sits in a blood - vessel near motor cortex. - sort_order: 2 - - name: BrainGate - url: https://www.braingate.org - description: > - Academic BCI research consortium. Pioneered intracortical recording for - cursor control and robotic arm operation. - detail: > - Read the published papers - they are the scientific foundation for commercial - BCIs like Neuralink. - sort_order: 3 - - name: Kernel Flow - url: https://www.kernel.co - description: > - Non-invasive neuroimaging headset using time-domain fNIRS to measure brain - hemodynamics. - detail: > - Study the non-invasive BCI approach - lower resolution but no surgery. - Important for consumer applications. - sort_order: 4 - - name: Paradromics - url: https://paradromics.com - description: > - High-bandwidth implantable BCI targeting speech restoration with 65,000+ - electrode channels. - detail: > - Track the high-channel-count approach - more data from the brain means - richer control signals. - sort_order: 5 - - name: OpenBCI - url: https://openbci.com - description: > - Open-source EEG hardware and software for brain-computer interface research - and development. - detail: > - Hands-on entry point. Buy the Cyton board and start reading your own EEG - signals. - sort_order: 6 - - - id: wearables - title: Wearables & Sensors - objective: > - Turn your body into a data stream - wearables, biosensors, and continuous - health monitoring. - color: "#5bb86e" - sort_order: 3 - resources: - - name: Whoop - url: https://www.whoop.com - description: > - Continuous strain, recovery, and sleep tracking via PPG and accelerometer. - Used by elite athletes. - detail: > - Study the recovery score algorithm - HRV, resting HR, respiratory rate, - sleep quality combined. - sort_order: 1 - - name: Oura Ring - url: https://ouraring.com - description: > - Ring-form-factor health tracker measuring sleep stages, HRV, SpO2, skin - temperature, and activity. - detail: > - Best sleep tracker on the market. Study how temperature trends predict - illness and cycle phases. - sort_order: 2 - - name: Apple Watch Health Sensors - url: https://www.apple.com/apple-watch/health/ - description: > - ECG, blood oxygen, temperature sensing, crash detection. FDA-cleared - medical-grade sensors in a consumer device. - detail: > - Track Apple's health sensor roadmap - blood pressure and glucose monitoring - are coming. - sort_order: 3 - - name: Dexcom CGM - url: https://www.dexcom.com - description: > - Continuous glucose monitoring - real-time blood sugar tracking via - subcutaneous sensor. - detail: > - Even non-diabetics use CGMs. Study how real-time glucose data changes eating - and exercise behavior. - sort_order: 4 - - name: Levels Health - url: https://www.levels.com - description: > - Metabolic health platform built on CGM data - glucose response scoring and - dietary optimization. - detail: > - Study the data layer on top of hardware: how raw sensor data becomes - actionable health insights. - sort_order: 5 - - name: Muse Headband - url: https://choosemuse.com - description: > - Consumer EEG headband for meditation and neurofeedback with real-time brain - state visualization. - detail: > - Entry-level brain sensing. Limited but useful for understanding EEG signals - and neurofeedback loops. - sort_order: 6 - - - id: biohacking - title: Biohacking - objective: > - Optimize your body systematically - sleep, nutrition, longevity, and - performance protocols. - color: "#eb5757" - sort_order: 4 - resources: - - name: Huberman Lab Podcast - url: https://www.hubermanlab.com - description: > - Stanford neuroscientist covering sleep, focus, hormones, exercise science, - and brain optimization protocols. - detail: > - The single best source for evidence-based protocols. Start with sleep, light - exposure, and dopamine episodes. - sort_order: 1 - - name: Peter Attia - The Drive - url: https://peterattiamd.com - description: > - Longevity medicine: exercise, nutrition, sleep, pharmacology, and healthspan - optimization frameworks. - detail: > - Deep dives on Zone 2 training, VO2max, cancer screening, and the longevity - framework from his book Outlive. - sort_order: 2 - - name: Bryan Johnson Blueprint - url: https://blueprint.bryanjohnson.com - description: > - Extreme longevity protocol: 100+ supplements, strict diet, biomarker - tracking, organ-age measurements. - detail: > - Study as the extreme end of the spectrum. The data collection methodology is - more valuable than the specific protocols. - sort_order: 3 - - name: Examine.com - url: https://examine.com - description: > - Evidence-based supplement and nutrition database with effect sizes, dosages, - and study quality ratings. - detail: > - Always check here before taking any supplement. Look at the Human Effect - Matrix for real evidence. - sort_order: 4 - - name: Skin Care by Hyram / Dr. Dray - url: https://www.youtube.com/@DrDray - description: > - Dermatologist covering evidence-based skincare: retinoids, SPF, active - ingredients, and anti-aging science. - detail: > - Cut through beauty marketing. Focus on the core actives: retinol, vitamin C, - niacinamide, SPF, AHA/BHA. - sort_order: 5 - - name: FoundMyFitness (Rhonda Patrick) - url: https://www.foundmyfitness.com - description: > - Micronutrient science, sauna protocols, cold exposure, genetics-based health - optimization. - detail: > - Deeper biochemistry than Huberman. Study the sulforaphane, omega-3, and - sauna/cold exposure episodes. - sort_order: 6 - - - id: translation - title: Live Translation - objective: > - Speak any language in real time - translation devices and multilingual AI for - your daily life. - color: "#5e6ad2" - sort_order: 5 - resources: - - name: Meta SeamlessM4T - url: https://ai.meta.com/research/seamless-communication/ - description: > - Multimodal translation model supporting speech-to-speech, speech-to-text, - and text-to-speech across 100+ languages. - detail: > - State of the art for multilingual AI. Study the architecture - it handles - EN/FR/ZH in a single model. - sort_order: 1 - - name: Google Translate + Pixel Buds - url: https://store.google.com/category/earbuds - description: > - Real-time conversation translation via earbuds. Google Translate supports 133 - languages with neural MT. - detail: > - The consumer baseline. Test the conversation mode with French and Chinese - - note latency and accuracy limits. - sort_order: 2 - - name: Timekettle Translator Earbuds - url: https://www.timekettle.co - description: > - Dedicated translation earbuds (WT2, M3) supporting 40+ languages with - bidirectional real-time translation. - detail: > - Purpose-built hardware vs phone-based translation. Study the dual-earpiece - sharing mode for conversations. - sort_order: 3 - - name: DeepL Translator - url: https://www.deepl.com - description: > - Highest-quality text translation, particularly strong for European languages - and increasingly for Chinese. - detail: > - Best for written translation quality. Compare its FR-EN and ZH-EN output - against Google Translate. - sort_order: 4 - - name: Whisper (OpenAI) - url: https://openai.com/research/whisper - description: > - Open-source speech recognition model supporting 99 languages with strong - multilingual performance. - detail: > - The ASR backbone for many translation pipelines. Study how it handles - code-switching (mixing FR/EN/ZH). - sort_order: 5 - - name: NLLB (No Language Left Behind) - url: https://ai.meta.com/research/no-language-left-behind/ - description: > - Meta's open-source translation model covering 200+ languages, especially - low-resource languages. - detail: > - Understand the training methodology. Important for the multilingual AI - landscape beyond just EN/FR/ZH. - sort_order: 6 - - - id: convergence - title: Convergence - objective: > - Where all of this converges - augmented humans and the near-future of - human-machine fusion. - color: "#f472b6" - sort_order: 6 - resources: - - name: Neuralink + AI Agents - url: https://waitbutwhy.com/2017/04/neuralink.html - description: > - Tim Urban's deep dive on Neuralink and the case for brain-computer bandwidth - as the bottleneck for human-AI symbiosis. - detail: > - Essential reading. Covers the biological stack from neurons to cortex and why - bandwidth matters. - sort_order: 1 - - name: Ray Kurzweil - The Singularity Is Nearer - url: https://www.singularityisnearer.com - description: > - Updated predictions on human-machine merger timelines, nanobots, brain - uploading, and exponential technology curves. - detail: > - Read critically. The timeline predictions are optimistic but the technology - convergence analysis is valuable. - sort_order: 2 - - name: DARPA Biological Technologies Office - url: https://www.darpa.mil/about/offices/bto - description: > - Military R&D programs in neural interfaces, biosensors, human performance - enhancement, and bio-machine interfaces. - detail: > - DARPA funds the frontier. Track programs like N3 (non-surgical neural - interfaces) and BTO biosecurity work. - sort_order: 3 - - name: Humanity+ / World Transhumanist Association - url: https://www.humanityplus.org - description: > - Transhumanist community and ethics framework for human enhancement - technologies. - detail: > - Study the ethical frameworks. Augmentation raises questions about access, - inequality, identity, and regulation. - sort_order: 4 - - name: Biohacking Village (DEF CON) - url: https://www.villageb.io - description: > - DEF CON community exploring security and hacking of medical devices, - implants, and biosensors. - detail: > - The security angle. When your body runs software, attack surfaces become - deeply personal. - sort_order: 5 - - name: IEEE Brain Initiative - url: https://brain.ieee.org - description: > - IEEE's standards and research coordination for neurotechnology, brain data, - and neuroethics. - detail: > - Track the standardization efforts. Interoperability standards will shape who - can build for the brain. - sort_order: 6 diff --git a/curriculum/tracks/cognitive-toolkit.yaml b/curriculum/tracks/cognitive-toolkit.yaml deleted file mode 100644 index 2e8d55a3..00000000 --- a/curriculum/tracks/cognitive-toolkit.yaml +++ /dev/null @@ -1,1041 +0,0 @@ -id: cognitive-toolkit -title: Cognitive Toolkit -description: > - Mental models, decision frameworks, and cognitive techniques for better thinking. -difficulty: intermediate -track_type: resource -category: product -category_order: 3 -track_order: 2 -modules: - - id: foundation - title: Foundation - objective: > - The mental models that shape how people decide, buy, and follow. - sort_order: 1 - resources: - - name: "Daniel Kahneman - Thinking, Fast and Slow" - description: "The two systems: fast/emotional vs slow/rational. Your character knows most people run on System 1. He designs for System 1." - resource_type: knowledge - sort_order: 1 - - name: "Richard Thaler & Cass Sunstein - Nudge" - description: "\"Choice architecture\" - you don't force people, you design the environment so the desired choice is the path of least resistance. Your character calls himself a \"choice architect.\"" - resource_type: knowledge - sort_order: 2 - - name: "Captology (B.J. Fogg, Stanford)" - description: "The science of persuasive technology - designing software to be psychologically compulsive. Your character's product team has a captology expert." - resource_type: knowledge - sort_order: 3 - - name: "Gregory Bateson - Steps to an Ecology of Mind" - description: "Change the environment, change the mind. Your character doesn't try to convince anyone - he reshapes the information ecology." - resource_type: knowledge - sort_order: 4 - - name: "Gustave Le Bon - Psychology of Crowds" - description: "Crowds are emotional, suggestible, and need simple narratives. Your character designs for crowds, not individuals." - resource_type: knowledge - sort_order: 5 - - - id: operating_system - title: Operating System - objective: > - Build systems that self-correct - feedback loops and adaptive behavioral control. - sort_order: 2 - resources: - - name: Measure - description: "People's behavior, emotions, attention (the data)." - resource_type: knowledge - sort_order: 1 - - name: Adjust - description: "Tweak the algorithm, the feed, the notification timing." - resource_type: knowledge - sort_order: 2 - - name: Observe - description: "Did they comply? Did engagement go up? Did resistance drop?" - resource_type: knowledge - sort_order: 3 - - name: Push - description: "Go further. Test the next boundary." - resource_type: knowledge - sort_order: 4 - - name: Repeat - description: "The loop never stops." - resource_type: knowledge - sort_order: 5 - - - id: techniques - title: Techniques - objective: > - Practical techniques for capturing attention and building habits that stick. - sort_order: 3 - resources: - - name: Attention economy - description: "Treats human attention as an extractable resource - the \"new oil.\"" - detail: "Euphemism: Engagement metrics" - resource_type: knowledge - sort_order: 1 - - name: Persona management software - description: "Fake profiles, bot armies, manufactured consensus. AI-powered = scales to millions." - detail: "Euphemism: Community management tools" - resource_type: knowledge - sort_order: 2 - - name: Astroturfing - description: "Manufacturing the appearance of popular support." - detail: "Euphemism: Grassroots activation / organic growth campaigns" - resource_type: knowledge - sort_order: 3 - - name: Nudge architecture - description: "Designing so people do what you want while feeling they chose it." - detail: "Euphemism: UX optimization / default settings" - resource_type: knowledge - sort_order: 4 - - name: Captology - description: "Slot-machine psychology: variable reward schedules, streaks, notifications timed to dopamine cycles." - detail: "Euphemism: Engagement loops / retention features" - resource_type: knowledge - sort_order: 5 - - name: Schismogenesis - description: "Fragmenting users into opposed bubbles so they fight each other instead of questioning the platform." - detail: "Euphemism: Community segmentation / personalized experiences" - resource_type: knowledge - sort_order: 6 - - name: Scotomization - description: "Keeping users inside comfortable information bubbles so they never encounter reality." - detail: "Euphemism: Content curation / safe spaces" - resource_type: knowledge - sort_order: 7 - - name: Carpet bombing - description: "Constant information overload that exhausts critical thinking." - detail: "Euphemism: Always-on content strategy" - resource_type: knowledge - sort_order: 8 - - name: Double bind - description: "Users can't tell if their emotions are valid or if they're being manipulated - so they defer to the platform." - detail: "Euphemism: Trust & Safety" - resource_type: knowledge - sort_order: 9 - - name: Character assassination - description: "Selectively destroying reputations of people who threaten the ecosystem." - detail: "Euphemism: Content moderation / community guidelines enforcement" - resource_type: knowledge - sort_order: 10 - - name: Compromat - description: "You have everything on everyone. You never use it - until you need to." - detail: "Euphemism: Data retention policies" - resource_type: knowledge - sort_order: 11 - - - id: worldview - title: Worldview - objective: > - The worldview behind persuasion systems - where optimization meets manipulation. - sort_order: 4 - resources: - - name: On users - description: "They're not customers. They're the product. But they can't know that. The first rule is: conceal the objective." - detail: "Direct from Bateson's 1941 declaration." - resource_type: knowledge - sort_order: 1 - - name: On democracy - description: "We don't interfere with democracy. We are democracy now. More people vote on our platform every day than in any election." - resource_type: knowledge - sort_order: 2 - - name: On regulation - description: "The DSA, GDPR - they think they're regulating us. They're actually creating barriers to entry for competitors. We want regulation. We just want to write it." - resource_type: knowledge - sort_order: 3 - - name: On critics - description: "'Conspiracist' is the most efficient word in the language - it makes everything they say radioactive without you having to address a single fact." - resource_type: knowledge - sort_order: 4 - - name: On emotions - description: "Fear of one's own emotions is more useful than the emotions themselves. A user who's afraid of being angry is a user who self-censors. A user who self-censors is a user who stays on platform." - detail: "The video's concept of induced psychosis - people becoming afraid of their own reactions." - resource_type: knowledge - sort_order: 5 - - name: On crises - description: "Never create a crisis. That's amateur. Just be ready. When it comes - and it always comes - you ride it. That's the judo move." - detail: "Naomi Klein's Shock Doctrine, reframed for tech." - resource_type: knowledge - sort_order: 6 - - name: On the agentic state - description: "The beautiful thing about process is that nobody's responsible. The algorithm did it. The policy required it. The AI recommended it. Nobody pulled the trigger, but the shot was fired." - detail: "Milgram, scaled to technology." - resource_type: knowledge - sort_order: 7 - - - id: library - title: Library - objective: > - The books and essays behind this playbook - your reading list for cognitive leverage. - sort_order: 5 - resources: - # Books actually read - - name: "Thinking, Fast and Slow" - description: "Cognitive biases as exploitable features." - detail: "Daniel Kahneman - actually read" - resource_type: knowledge - sort_order: 1 - - name: Nudge - description: "Choice architecture manual." - detail: "Richard Thaler & Cass Sunstein - actually read" - resource_type: knowledge - sort_order: 2 - - name: Steps to an Ecology of Mind - description: "Environment shapes cognition." - detail: "Gregory Bateson - actually read" - resource_type: knowledge - sort_order: 3 - - name: Psychology of Crowds - description: "Crowd behavior is predictable." - detail: "Gustave Le Bon - actually read" - resource_type: knowledge - sort_order: 4 - - name: The Lucifer Principle - description: "How systems recycle dissent to sustain themselves." - detail: "Howard Bloom - actually read" - resource_type: knowledge - sort_order: 5 - # Books displayed (performative) - - name: Meditations - description: "Performative stoicism for boardroom credibility." - detail: "Marcus Aurelius - displayed for guests" - resource_type: knowledge - sort_order: 6 - - name: Some Stoicism bestseller - description: "Signals intellectual depth without revealing actual playbook." - detail: "Various - displayed for guests" - resource_type: knowledge - sort_order: 7 - # Books feared - - name: We - description: "The original dystopia - describes the world he's building." - detail: "Yevgeny Zamyatin - feared" - resource_type: knowledge - sort_order: 8 - - name: The Shock Doctrine - description: "Documents exactly how crises are exploited. Too close to home." - detail: "Naomi Klein - feared" - resource_type: knowledge - sort_order: 9 - - name: The Culture of Narcissism - description: "Describes the psychological landscape he depends on." - detail: "Christopher Lasch - feared" - resource_type: knowledge - sort_order: 10 - - - id: playbook - title: Playbook - objective: > - Turn theory into daily practice - routines and checklists for product teams. - sort_order: 6 - resources: - # Feed & Content Design - - name: Infinite scroll - description: "Eliminates natural stopping points. Removes the pagination break that would let users disengage." - detail: "Category: Feed & Content Design" - resource_type: knowledge - sort_order: 1 - - name: Autoplay next - description: "Netflix auto-plays with a countdown. YouTube chains the next video. Removes the active decision to continue." - detail: "Category: Feed & Content Design" - resource_type: knowledge - sort_order: 2 - - name: Pull-to-refresh - description: "Mimics a slot machine lever. Variable reward each time you pull down. Based on Skinner's variable ratio reinforcement schedule." - detail: "Category: Feed & Content Design" - resource_type: knowledge - sort_order: 3 - - name: Algorithmic feeds - description: "Replaced chronological timelines (~2016). Optimizes for engagement, not recency. Makes the feed feel \"infinite\" because there's always something relevant." - detail: "Category: Feed & Content Design" - resource_type: knowledge - sort_order: 4 - # Social Validation Loops - - name: Like/reaction counts - description: "Public social proof. Creates both posting incentive (seeking likes) and browsing incentive (seeing what's popular)." - detail: "Category: Social Validation Loops" - resource_type: knowledge - sort_order: 5 - - name: View/play counts - description: "Signals popularity, creates bandwagon effect, and gives creators a score to chase." - detail: "Category: Social Validation Loops" - resource_type: knowledge - sort_order: 6 - - name: Read receipts & typing indicators - description: "Creates social pressure to respond promptly. Turns every conversation into a real-time obligation." - detail: "Category: Social Validation Loops" - resource_type: knowledge - sort_order: 7 - - name: Streak mechanics - description: "Snapchat streaks, Duolingo streaks, GitHub contribution graphs. Loss aversion keeps users returning daily." - detail: "Category: Social Validation Loops" - resource_type: knowledge - sort_order: 8 - - name: Follower counts - description: "Public scoreboard. Gamifies social relationships." - detail: "Category: Social Validation Loops" - resource_type: knowledge - sort_order: 9 - # Notifications & Re-engagement - - name: Push notification batching - description: "\"3 people liked your post\" is more compelling than individual notifications. Creates curiosity gap." - detail: "Category: Notifications & Re-engagement" - resource_type: knowledge - sort_order: 10 - - name: FOMO notifications - description: "\"You're missing posts from [person]\", \"A post is getting lots of engagement nearby.\" Manufactured urgency." - detail: "Category: Notifications & Re-engagement" - resource_type: knowledge - sort_order: 11 - - name: Email digests - description: "\"Here's what you missed\", \"X posted for the first time in a while.\" Re-engagement hooks." - detail: "Category: Notifications & Re-engagement" - resource_type: knowledge - sort_order: 12 - - name: Badge counts - description: "Red notification dots. Exploits completionism - people feel compelled to \"clear\" them." - detail: "Category: Notifications & Re-engagement" - resource_type: knowledge - sort_order: 13 - - name: Time-delayed notifications - description: "Not sent immediately. Timed for when you're likely idle (e.g., after lunch). Maximizes re-open rate." - detail: "Category: Notifications & Re-engagement" - resource_type: knowledge - sort_order: 14 - # Variable Reward Mechanics - - name: Loot box / mystery reward patterns - description: "TikTok's For You page is essentially a slot machine. Each swipe is a pull. The unpredictability is the hook." - detail: "Category: Variable Reward Mechanics" - resource_type: knowledge - sort_order: 15 - - name: Stories that disappear - description: "Scarcity + FOMO. Content expires in 24h so you check frequently." - detail: "Category: Variable Reward Mechanics" - resource_type: knowledge - sort_order: 16 - - name: Refresh randomization - description: "Same feed, different order on refresh. Teaches users that \"there might be something new\" even when there isn't." - detail: "Category: Variable Reward Mechanics" - resource_type: knowledge - sort_order: 17 - # Content Format Optimization - - name: Short-form video - description: "Low commitment per unit = more units consumed. Completion rates are high, so the algorithm gets rapid signal." - detail: "Category: Content Format Optimization" - resource_type: knowledge - sort_order: 18 - - name: Vertical full-screen - description: "Eliminates peripheral distractions. Total immersion. One piece of content = your entire visual field." - detail: "Category: Content Format Optimization" - resource_type: knowledge - sort_order: 19 - - name: Sound-on defaults - description: "TikTok made audio integral. Creates a more immersive, harder-to-disengage-from experience." - detail: "Category: Content Format Optimization" - resource_type: knowledge - sort_order: 20 - - name: Clickbait optimization - description: "Thumbnail A/B testing (MrBeast style). Curiosity gap headlines. Platforms reward high CTR." - detail: "Category: Content Format Optimization" - resource_type: knowledge - sort_order: 21 - # Friction Asymmetry - - name: Easy to start, hard to stop - description: "Sign-up is one click (SSO). Deleting your account is buried 6 menus deep." - detail: "Category: Friction Asymmetry" - resource_type: knowledge - sort_order: 22 - - name: Dark patterns in unsubscribe - description: "\"Are you sure?\" then \"You'll lose your data\" then \"How about a discount?\" then \"Call us to cancel.\"" - detail: "Category: Friction Asymmetry" - resource_type: knowledge - sort_order: 23 - - name: Default-on settings - description: "Autoplay, notifications, data sharing are all opt-out, not opt-in." - detail: "Category: Friction Asymmetry" - resource_type: knowledge - sort_order: 24 - - name: One-click purchasing - description: "Amazon's patent. Removes the friction that would give you a moment to reconsider." - detail: "Category: Friction Asymmetry" - resource_type: knowledge - sort_order: 25 - # Personalization & Lock-in - - name: Recommendation engines - description: "The more you use it, the better it gets, the harder it is to switch (data moat)." - detail: "Category: Personalization & Lock-in" - resource_type: knowledge - sort_order: 26 - - name: Collaborative filtering - description: "\"People like you also watched...\" Creates a sense that the platform \"knows you.\"" - detail: "Category: Personalization & Lock-in" - resource_type: knowledge - sort_order: 27 - - name: Taste profiles as identity - description: "Spotify Wrapped, Netflix percentages (\"98% match\"). Makes the algorithm feel like a personal relationship." - detail: "Category: Personalization & Lock-in" - resource_type: knowledge - sort_order: 28 - - name: Social graph lock-in - description: "Your friends are here. Moving platforms means losing connections. Network effects as retention." - detail: "Category: Personalization & Lock-in" - resource_type: knowledge - sort_order: 29 - # Temporal Manipulation - - name: Hiding time indicators - description: "TikTok and Instagram removed clocks / made them less visible during use. Users lose track of time." - detail: "Category: Temporal Manipulation" - resource_type: knowledge - sort_order: 30 - - name: Removing natural endpoints - description: "No \"you're all caught up.\" No finite playlist. No \"end of feed.\"" - detail: "Category: Temporal Manipulation" - resource_type: knowledge - sort_order: 31 - - name: Live content - description: "Twitch, Instagram Live, Twitter Spaces. \"If you leave, you miss it.\" Synchronous FOMO." - detail: "Category: Temporal Manipulation" - resource_type: knowledge - sort_order: 32 - - name: Limited-time events - description: "Fortnite concerts, Snapchat Discover, Instagram drops. Manufactured urgency." - detail: "Category: Temporal Manipulation" - resource_type: knowledge - sort_order: 33 - # Gamification - - name: Progress bars - description: "LinkedIn \"profile completeness.\" Exploits the Zeigarnik effect (incomplete tasks nag at you)." - detail: "Category: Gamification" - resource_type: knowledge - sort_order: 34 - - name: Achievements / badges - description: "Reddit karma, Stack Overflow reputation, Google Maps Local Guide levels." - detail: "Category: Gamification" - resource_type: knowledge - sort_order: 35 - - name: Leaderboards - description: "Apple Screen Time comparisons, Fitbit friend rankings. Competition drives engagement." - detail: "Category: Gamification" - resource_type: knowledge - sort_order: 36 - - name: Creator monetization tiers - description: "YouTube Partner Program thresholds, TikTok Creator Fund. Turns users into employees with KPIs." - detail: "Category: Gamification" - resource_type: knowledge - sort_order: 37 - # Structural / Platform-Level - - name: Cross-app deep linking - description: "WhatsApp shares open in Instagram, which opens in Chrome, which opens the app store. Every app captures you into its ecosystem." - detail: "Category: Structural / Platform-Level" - resource_type: knowledge - sort_order: 38 - - name: Super app bundling - description: "WeChat (messaging + payments + social + shopping). Once you're in, everything is there. No reason to leave." - detail: "Category: Structural / Platform-Level" - resource_type: knowledge - sort_order: 39 - - name: Platform-controlled distribution - description: "Creators depend on the algorithm. They must post frequently, in the right format, at the right time. The platform sets the rules, creators comply." - detail: "Category: Structural / Platform-Level" - resource_type: knowledge - sort_order: 40 - - - id: operators - title: Operators - objective: > - The people who run these systems - archetypes, roles, and how they operate. - sort_order: 7 - resources: - # Behavioral models - - name: "B.J. Fogg - Behavior = MAP" - description: "Motivation + Ability + Prompt must converge at the same moment. Key insight: don't increase motivation - reduce friction." - detail: "His Stanford students went on to found/lead growth at Instagram, LinkedIn, Fitbit." - resource_type: knowledge - sort_order: 1 - - name: "Nir Eyal - The Hook Model" - description: "Trigger (external then internal) -> Action (simplest behavior) -> Variable Reward (tribe/hunt/self) -> Investment (data, content, reputation). Investment loads the next trigger." - resource_type: knowledge - sort_order: 2 - - name: "B.F. Skinner - Variable Ratio Reinforcement" - description: "A pigeon rewarded on a random schedule presses the lever compulsively. Mathematically identical to a slot machine, a social media feed, and a loot box." - detail: "Skinner (1986): \"The gambling industry is the largest application of my work.\" Social media is the second largest." - resource_type: knowledge - sort_order: 3 - - name: "Robert Cialdini - Six Weapons of Influence" - description: "Social proof, Reciprocity, Scarcity, Authority, Commitment/Consistency, Liking - applied at platform scale." - resource_type: knowledge - sort_order: 4 - # Cialdini principles detail - - name: "Cialdini - Social proof" - description: "\"1.2M views\", \"Your friend liked this\", star ratings." - detail: "Category: Six Weapons of Influence" - resource_type: knowledge - sort_order: 5 - - name: "Cialdini - Reciprocity" - description: "Free trials, free storage, \"we gave you something, now sign up.\"" - detail: "Category: Six Weapons of Influence" - resource_type: knowledge - sort_order: 6 - - name: "Cialdini - Scarcity" - description: "\"Only 2 left!\", Stories that expire, limited drops." - detail: "Category: Six Weapons of Influence" - resource_type: knowledge - sort_order: 7 - - name: "Cialdini - Authority" - description: "Verified badges, \"recommended by experts\", editorial picks." - detail: "Category: Six Weapons of Influence" - resource_type: knowledge - sort_order: 8 - - name: "Cialdini - Commitment / Consistency" - description: "Profile completion bars, streaks, sunk cost of curated playlists." - detail: "Category: Six Weapons of Influence" - resource_type: knowledge - sort_order: 9 - - name: "Cialdini - Liking" - description: "Personalized UI, friendly tone, avatars, \"people like you.\"" - detail: "Category: Six Weapons of Influence" - resource_type: knowledge - sort_order: 10 - # Company case studies - - name: "Meta (Facebook / Instagram)" - description: "The Most Scientifically Rigorous. 2012 emotional contagion study, \"People You May Know\", notification tuning, the Like button, Facebook Files (Haugen 2021), Chamath Palihapitiya growth critique." - resource_type: knowledge - sort_order: 11 - - name: "TikTok (ByteDance)" - description: "The Algorithm Perfected. Zero-friction entry, full-screen vertical video, multi-armed bandit algorithm, micro-reward variability tuning, Chinese version has age limits the international version lacks." - resource_type: knowledge - sort_order: 12 - - name: "Google / YouTube" - description: "Radicalization by Recommendation. Guillaume Chaslot's AlgoTransparency, 2012 watch-time shift, YouTube Kids Elsagate, 70% of watch time from recommendations." - resource_type: knowledge - sort_order: 13 - - name: "Snap (Snapchat)" - description: "Weaponized Social Pressure. Snapstreaks and loss aversion, Snap Map social surveillance, disappearing content urgency." - resource_type: knowledge - sort_order: 14 - - name: Apple - description: "Friction Removal as Empire. Face ID unlocking frequency, Apple Pay spending psychology, Screen Time as cure for their own disease." - resource_type: knowledge - sort_order: 15 - - name: Amazon - description: "Purchase Behavior Engineering. One-Click Buy patent, collaborative filtering (+35% basket), Prime sunk cost, Subscribe & Save automation, Project Iliad cancellation obstruction (FTC sued 2023)." - resource_type: knowledge - sort_order: 16 - - name: Netflix - description: "Eliminating the Decision to Stop. Autoplay next episode, post-play previews, skip intro, simplified rating system." - resource_type: knowledge - sort_order: 17 - # Backlash figures - - name: Tristan Harris - description: "Founded Center for Humane Technology. Star of The Social Dilemma (2020). Coined \"the attention economy\" framing." - detail: "ex-Google design ethicist" - resource_type: knowledge - sort_order: 18 - - name: Aza Raskin - description: "\"It's as if they took behavioral cocaine and just sprinkled it all over your interface.\"" - detail: "inventor of infinite scroll" - resource_type: knowledge - sort_order: 19 - - name: Roger McNamee - description: "Wrote Zucked (2019), arguing Facebook knowingly exploited psychological vulnerabilities." - detail: "early Facebook investor" - resource_type: knowledge - sort_order: 20 - - - id: social_dynamics - title: Social Dynamics - objective: > - Applied psychology for one-on-one interactions - influence, rapport, and social dynamics. - sort_order: 8 - resources: - # Core Principles - - name: Non-neediness - description: "Neediness is the single strongest repellent in interpersonal dynamics. It signals low status, scarcity mindset, and emotional dependency." - detail: "Willingness to walk away from any interaction. Not performing indifference - actually having a life full enough that no single person's validation matters." - resource_type: knowledge - sort_order: 1 - - name: Polarization over approval - description: "Trying to be liked by everyone produces lukewarm responses. Taking clear positions repels some and magnetizes others. Net effect: stronger connections with compatible people." - detail: "Express genuine preferences and opinions. Don't soften everything to avoid offense." - resource_type: knowledge - sort_order: 2 - - name: Vulnerability as signal strength - description: "Calibrated vulnerability signals confidence - you're secure enough to expose imperfection. Paradoxically increases perceived status." - detail: "Share genuine stories of failure, uncertainty, or emotion. The key word is 'genuine.' Performed vulnerability is detected instantly and backfires." - resource_type: knowledge - sort_order: 3 - - name: Investment asymmetry - description: "People value what they invest in (IKEA effect, cognitive dissonance). The person investing more in a relationship values it more." - detail: "Create opportunities for the other person to invest - ask for small favors (Ben Franklin effect), let them contribute ideas." - resource_type: knowledge - sort_order: 4 - - name: Pre-selection & social proof - description: "Humans outsource mate evaluation. Being seen as desired by others signals quality more efficiently than any self-presentation." - detail: "Mixed-gender social circles. Warm introductions. Being visibly valued by people whose opinion matters." - resource_type: knowledge - sort_order: 5 - - name: Frame control - description: "Whoever defines the frame of the interaction controls its dynamics. 'I'm interviewing for your approval' vs 'We're both figuring out if this is interesting' are radically different power structures." - detail: "Set collaborative frames ('Let's see if we vibe') not evaluative ones ('I hope you like me')." - resource_type: knowledge - sort_order: 6 - # High-Leverage Variables - - name: Volume of social exposure - description: "The single highest-ROI variable. Meeting 5 people a week vs 50 changes everything. Most 'game' advice is optimization on the margins compared to simply increasing throughput." - detail: "Category: High-Leverage Variables" - resource_type: knowledge - sort_order: 7 - - name: Physical presentation - description: "Not genetics - grooming, fit, posture, energy. The controllable 80%. Well-fitted clothes, clean skin, good sleep, and physical fitness outperform any conversational technique." - detail: "Category: High-Leverage Variables" - resource_type: knowledge - sort_order: 8 - - name: Status signaling - description: "Competence in a visible domain. Not wealth display - demonstrated skill, social proof, leadership in a group." - detail: "Category: High-Leverage Variables" - resource_type: knowledge - sort_order: 9 - - name: Comfort with rejection - description: "The ability to be rejected without it affecting your state. This is trained, not innate. Each rejection that doesn't destroy you raises your baseline confidence." - detail: "Category: High-Leverage Variables" - resource_type: knowledge - sort_order: 10 - # Conversation Mechanics - - name: Statements > questions - description: "'You look like you're from somewhere interesting' beats 'Where are you from?' Statements invite collaboration. Questions demand performance." - detail: "Category: Conversation Mechanics" - resource_type: knowledge - sort_order: 11 - - name: Cold reads - description: "Making an assumption about someone ('You're definitely the organized friend in your group'). If right, they feel seen. If wrong, they correct you - either way, rapport." - detail: "Category: Conversation Mechanics" - resource_type: knowledge - sort_order: 12 - - name: Push-pull - description: "Alternating between showing interest and playful disqualification. Creates emotional range. Monotone positivity (or negativity) flatlines engagement." - detail: "Category: Conversation Mechanics" - resource_type: knowledge - sort_order: 13 - - name: Active disinterest in outcome - description: "Paradox: the less you try to steer the conversation toward a specific outcome, the more natural and attractive the interaction becomes." - detail: "Category: Conversation Mechanics" - resource_type: knowledge - sort_order: 14 - - name: Escalation through honesty - description: "Rather than manufactured moves - just say what you're thinking. 'I find you interesting and I'd like to keep talking' is more effective than any scripted line because it's rare." - detail: "Category: Conversation Mechanics" - resource_type: knowledge - sort_order: 15 - # Environmental Design - - name: Venue selection - description: "Where you spend time determines who you meet. Co-working spaces, sport clubs, language classes, gallery openings - environments self-select for shared interests." - detail: "Category: Environmental Design" - resource_type: knowledge - sort_order: 16 - - name: Social circle engineering - description: "Your network is your net worth - socially too. Host events. Be the connector. The person who brings people together occupies the highest-status node in the graph." - detail: "Category: Environmental Design" - resource_type: knowledge - sort_order: 17 - - name: Proximity + repeated exposure - description: "The mere exposure effect: familiarity breeds attraction. Regulars at the same cafe, gym, class. Repeated low-stakes encounters build comfort before any 'approach.'" - detail: "Category: Environmental Design" - resource_type: knowledge - sort_order: 18 - - name: Context switching - description: "Meeting someone at a bar vs meeting them at a climbing gym vs meeting them through a friend. Same people, different frames. Non-nightlife contexts reduce evaluative pressure." - detail: "Category: Environmental Design" - resource_type: knowledge - sort_order: 19 - # Inner Game - - name: Identity over technique - description: "Techniques are band-aids. 'What would an attractive person do?' is the wrong question. 'What kind of person do I want to be?' is the right one." - detail: "Category: Inner Game" - resource_type: knowledge - sort_order: 20 - - name: Outcome independence - description: "The single most repeated concept in every serious framework. If your emotional state depends on whether someone responds positively, you've already lost the frame." - detail: "Category: Inner Game" - resource_type: knowledge - sort_order: 21 - - name: Rejection as data - description: "Reframe: rejection isn't failure, it's a preference mismatch. She doesn't like you != you're unlikeable. It means this specific pairing doesn't work." - detail: "Category: Inner Game" - resource_type: knowledge - sort_order: 22 - - name: "The 'enough' threshold" - description: "You already have enough - skills, looks, stories, value. The belief in deficiency ('I need to be X first') is the actual obstacle. Readiness is a decision, not a state." - detail: "Category: Inner Game" - resource_type: knowledge - sort_order: 23 - # Anti-patterns - - name: "Anti-pattern: Scripted openers" - description: "Detectable within seconds. The person feels like a target, not a human. Any technique that requires memorization will feel incongruent." - detail: "Category: Anti-Patterns" - resource_type: knowledge - sort_order: 24 - - name: "Anti-pattern: Negging" - description: "Backhanded compliments signal insecurity, not confidence. Works only on people with low self-esteem - and that's not a filter you want to be using." - detail: "Category: Anti-Patterns" - resource_type: knowledge - sort_order: 25 - - name: "Anti-pattern: Excessive availability" - description: "Responding instantly every time, rearranging your schedule constantly, always saying yes. Signals you have nothing else going on." - detail: "Category: Anti-Patterns" - resource_type: knowledge - sort_order: 26 - - name: "Anti-pattern: Covert contracts" - description: "'I did X so she should give me Y.' Unspoken expectations breed resentment. If you're being nice with an agenda, it's not generosity - it's a transaction." - detail: "Category: Anti-Patterns" - resource_type: knowledge - sort_order: 27 - - name: "Anti-pattern: Identity performing" - description: "Pretending to be someone you're not is unsustainable. The gap between persona and person destroys trust." - detail: "Category: Anti-Patterns" - resource_type: knowledge - sort_order: 28 - # Reading list - - name: "Models (Mark Manson)" - description: "Anti-manipulation framework. Attraction through genuine non-neediness and honest self-expression. The best single book on the topic." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 29 - - name: "Influence (Robert Cialdini)" - description: "The six weapons work identically in romance as in sales. Reciprocity, scarcity, social proof - same mechanisms, different context." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 30 - - name: "The Game (Neil Strauss)" - description: "The pickup artist origin story. Useful as anthropology - documents what happens when you systematize seduction. Follow-up (The Truth) documents why it implodes." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 31 - - name: "The Art of Seduction (Robert Greene)" - description: "Historical archetypes of seducers. Treats seduction as a strategic art with character types: the Siren, the Rake, the Charmer, the Charismatic." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 32 - - name: "Attached (Amir Levine & Rachel Heller)" - description: "Attachment theory (anxious, avoidant, secure). Explains why some dynamics work on some people and self-destruct with others." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 33 - - name: "The Laws of Human Nature (Robert Greene)" - description: "Broader than dating - the emotional undercurrents driving all human behavior. Envy, narcissism, grandiosity, conformity." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 34 - - name: "Mating in Captivity (Esther Perel)" - description: "The tension between security and desire. Why domesticity kills attraction. How to maintain polarity in long-term relationships." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 35 - - name: "The Rational Male (Rollo Tomassi)" - description: "Evolutionary psychology lens on intersexual dynamics. Controversial framework - useful as a model, dangerous as an ideology. Read critically." - detail: "Category: Reading List" - resource_type: knowledge - sort_order: 36 - - - id: ai_leverage - title: AI Leverage - objective: > - High-ROI ways to use AI in your daily life - career, health, finance, and learning. - sort_order: 9 - resources: - # Dating & Social at Scale - - name: Profile Photo Optimization Pipeline - description: "Feed all your photos to GPT-4o and rank by dating app effectiveness. A/B test on Photofeeler. Enhance with Remini. Rotate top 3 weekly based on match rate data." - detail: "Domain: Dating & Social at Scale | Tool: GPT-4o Vision + Photofeeler + Remini AI | Difficulty: easy | Cost: $20/mo | Leverage: 2-3x match rate." - resource_type: knowledge - sort_order: 1 - - name: Bio & Opener Generation at Scale - description: "Feed Claude your personality traits, humor style, and 20 real messages. Generate bio variations per platform and context-specific openers." - detail: "Domain: Dating & Social at Scale | Tool: Claude with your voice profile | Difficulty: easy | Cost: $20/mo | Leverage: 3-5x response rate." - resource_type: knowledge - sort_order: 2 - - name: Social Calendar & Venue Discovery Engine - description: "AI as social coordinator: generates a weekly plan of events based on city, interests, schedule. Target: 3-4 social touchpoints per week." - detail: "Domain: Dating & Social at Scale | Tool: ChatGPT + Luma + Eventbrite + Zapier | Difficulty: medium | Cost: $20/mo | Leverage: Programmatic serendipity." - resource_type: knowledge - sort_order: 3 - - name: Date Logistics Optimizer - description: "Build a venue database tagged by neighborhood, vibe, price. Claude picks the optimal venue + suggests time slots based on her profile." - detail: "Domain: Dating & Social at Scale | Tool: Claude + personal venue database | Difficulty: medium | Cost: $20/mo | Leverage: 15-20 min saved per date." - resource_type: knowledge - sort_order: 4 - # Financial Optimization - - name: Negotiation Prep Engine - description: "Before any negotiation: brief Claude on positions, market data, your BATNA. Generate opening scripts, anticipate objections, role-play 3 rounds." - detail: "Domain: Financial Optimization | Tool: Claude with role-play | Difficulty: easy | Cost: $20/mo | Leverage: $5,000-30,000 per major negotiation." - resource_type: knowledge - sort_order: 5 - - name: Tax Optimization Research - description: "Upload previous return to Claude. Identifies missed deductions, entity structure changes, timing strategies. Prep to ask your CPA the right questions." - detail: "Domain: Financial Optimization | Tool: Claude (long context) + IRS publications | Difficulty: medium | Cost: $20/mo | Leverage: $2,000-15,000/year." - resource_type: knowledge - sort_order: 6 - - name: Investment Research Accelerator - description: "ChatGPT pulls earnings transcripts and reports. Claude analyzes competitive moat, key risks, bull/bear thesis. For building your own thesis faster." - detail: "Domain: Financial Optimization | Tool: ChatGPT browsing + Claude | Difficulty: medium | Cost: $20-40/mo | Leverage: 10x faster research." - resource_type: knowledge - sort_order: 7 - - name: Side Income Idea Validation - description: "Describe skills, available hours, capital. Claude generates ranked side income ideas by time-to-first-dollar, scalability, skill alignment." - detail: "Domain: Financial Optimization | Tool: Claude + Google Trends + ChatGPT | Difficulty: medium | Cost: $20/mo | Leverage: Compress weeks of market research into hours." - resource_type: knowledge - sort_order: 8 - - name: Deal & Price Intelligence - description: "For purchases over $200: best time to buy, price history, refurbished vs new analysis, retailer price-matching. Set up Make.com automations." - detail: "Domain: Financial Optimization | Tool: ChatGPT + CamelCamelCamel + Make.com | Difficulty: easy | Cost: Free-$20/mo | Leverage: 15-30% savings, $2,000-5,000/year." - resource_type: knowledge - sort_order: 9 - # Career & Status Building - - name: LinkedIn Content Machine - description: "One deep-thought post per week manually, then Claude extracts 3 derivative posts, generates comments for big accounts, repurposes into threads/newsletter." - detail: "Domain: Career & Status Building | Tool: Claude + Typefully | Difficulty: medium | Cost: $20 + $12/mo | Leverage: 5-10x content output." - resource_type: knowledge - sort_order: 10 - - name: Personal Brand Voice Training - description: "Feed Claude 20-30 of your best communications. It analyzes your voice patterns. Save as system prompt. Update quarterly." - detail: "Domain: Career & Status Building | Tool: Claude with system prompt | Difficulty: medium | Cost: $20/mo | Leverage: Eliminates 'sounds like AI' problem." - resource_type: knowledge - sort_order: 11 - - name: Strategic Networking Outreach - description: "Identify 50 people to know in 12 months. Claude generates personalized outreach referencing something specific. Track in CRM. Follow up every 6-8 weeks." - detail: "Domain: Career & Status Building | Tool: Claude + LinkedIn + Apollo.io | Difficulty: hard | Cost: $20 + $0-99/mo | Leverage: 10x higher response rate." - resource_type: knowledge - sort_order: 12 - - name: Interview Prep Simulation - description: "Give Claude the JD, company values, your resume. Full behavioral + technical interview over 3 rounds with feedback. ChatGPT voice for verbal practice." - detail: "Domain: Career & Status Building | Tool: Claude role-play + ChatGPT Voice | Difficulty: easy | Cost: $20/mo | Leverage: Equivalent to $300-500 coaching." - resource_type: knowledge - sort_order: 13 - - name: Ghostwritten Thought Leadership - description: "Record 10-minute voice memo. Claude extracts thesis, structures 1,200-word article, matches voice profile. Edit 15 min. Publish weekly." - detail: "Domain: Career & Status Building | Tool: Claude + voice memos + Midjourney | Difficulty: medium | Cost: $20 + $10/mo | Leverage: 4-6 hours reduced to 45 minutes." - resource_type: knowledge - sort_order: 14 - # Email & Communication - - name: Email Triage Automation - description: "Every incoming email classified by GPT-4 into 4 buckets: respond today, respond this week, FYI only, unsubscribe candidate. Auto-apply Gmail labels." - detail: "Domain: Email & Communication | Tool: GPT-4 API + Zapier + Gmail | Difficulty: hard | Cost: $20 + $20/mo Zapier | Leverage: 30-45 min/day saved." - resource_type: knowledge - sort_order: 15 - - name: Smart Reply Drafting - description: "Paste email thread, add one line about intent ('decline politely', 'negotiate timeline'). Claude drafts reply. For important emails, ask for 3 variations." - detail: "Domain: Email & Communication | Tool: Claude in browser | Difficulty: easy | Cost: $20/mo | Leverage: 60-70% time reduction." - resource_type: knowledge - sort_order: 16 - - name: Meeting Prep Briefs - description: "Before any meeting: paste their LinkedIn, company page, recent news. Get 1-page brief with talking points, questions, areas of mutual value." - detail: "Domain: Email & Communication | Tool: Claude + LinkedIn | Difficulty: easy | Cost: $20/mo | Leverage: Disproportionate social capital." - resource_type: knowledge - sort_order: 17 - - name: Personal CRM with AI Follow-ups - description: "Contact database with last interaction, relationship strength, topics discussed. Claude scans contacts not touched in 30+ days, drafts follow-ups." - detail: "Domain: Email & Communication | Tool: Clay.com or Notion + Claude | Difficulty: hard | Cost: $20 + $0-59/mo | Leverage: Maintain 150+ active relationships." - resource_type: knowledge - sort_order: 18 - - name: Difficult Conversation Scripting - description: "Before any hard conversation: Claude drafts opening using nonviolent communication, anticipates 3 emotional responses, gives de-escalation scripts." - detail: "Domain: Email & Communication | Tool: Claude with role-play | Difficulty: easy | Cost: $20/mo | Leverage: Removes avoidance instinct." - resource_type: knowledge - sort_order: 19 - # Health & Performance - - name: Custom Meal Plan Generator - description: "Give Claude macro targets, restrictions, cooking skill, budget, 10 foods you enjoy. Get 7-day plan with grocery list. Regenerate weekly." - detail: "Domain: Health & Performance | Tool: Claude + MyFitnessPal | Difficulty: easy | Cost: $20/mo | Leverage: Eliminates nutrition decision fatigue." - resource_type: knowledge - sort_order: 20 - - name: Periodized Workout Programming - description: "Give Claude training history, equipment, schedule, goals, injuries. 12-week periodized program with progressive overload. Adjusts weekly based on actual performance." - detail: "Domain: Health & Performance | Tool: Claude + training log | Difficulty: medium | Cost: $20/mo | Leverage: 30-50% better results vs random programming." - resource_type: knowledge - sort_order: 21 - - name: Supplement Stack Audit - description: "Give Claude current stack, goals, medications. Evaluates evidence grade per supplement, checks interactions, suggests dosage/timing and cheaper alternatives." - detail: "Domain: Health & Performance | Tool: Claude + Examine.com + PubMed | Difficulty: easy | Cost: $20/mo | Leverage: Prevents $100-300/month waste." - resource_type: knowledge - sort_order: 22 - - name: Sleep Data Analysis - description: "Export 30 days of sleep data. Claude identifies optimal bedtime window, behavior-quality correlations, environmental changes to test." - detail: "Domain: Health & Performance | Tool: Claude + Oura/Whoop data | Difficulty: medium | Cost: $20/mo + wearable | Leverage: 10% sleep improvement cascades." - resource_type: knowledge - sort_order: 23 - - name: Doctor Visit Prep - description: "Describe symptoms, timeline, history. Claude generates differential diagnosis list, questions to ask, tests to request, urgency flags. Print and bring." - detail: "Domain: Health & Performance | Tool: Claude + medical history | Difficulty: easy | Cost: $20/mo | Leverage: 3x more value from 15-minute visits." - resource_type: knowledge - sort_order: 24 - # Legal & Admin - - name: Contract & Lease Review - description: "Paste any contract. Claude summarizes obligations, flags unusual clauses, identifies missing protections, compares to standards, drafts amendments." - detail: "Domain: Legal & Admin | Tool: Claude (200k context) | Difficulty: easy | Cost: $20/mo | Leverage: Saves $500-2,000 per contract." - resource_type: knowledge - sort_order: 25 - - name: Dispute Resolution Letters - description: "Give Claude facts, regulations, desired outcome. Drafts escalation sequence: polite request, formal demand, regulatory complaint, small claims prep." - detail: "Domain: Legal & Admin | Tool: Claude with legal tone | Difficulty: medium | Cost: $20/mo | Leverage: Resolves 70-80% of disputes without litigation." - resource_type: knowledge - sort_order: 26 - - name: Bureaucracy Navigation - description: "For any government process: ChatGPT finds forms, deadlines, requirements. Claude builds step-by-step plan with common pitfalls and sequencing issues." - detail: "Domain: Legal & Admin | Tool: ChatGPT browsing + Claude | Difficulty: medium | Cost: $20/mo | Leverage: 5-20 hours saved per process." - resource_type: knowledge - sort_order: 27 - - name: Real Estate Analysis - description: "Paste listing and comps. Claude calculates true ownership cost, flags red flags, estimates negotiation room, compares to renting." - detail: "Domain: Legal & Admin | Tool: Claude + Zillow/Redfin data | Difficulty: medium | Cost: $20/mo | Leverage: $10,000-15,000 better negotiation." - resource_type: knowledge - sort_order: 28 - - name: Insurance Claim Optimization - description: "Upload policy. Claude checks coverage per specific language, drafts claim using matching terminology, identifies documentation needed, drafts appeal letters." - detail: "Domain: Legal & Admin | Tool: Claude + policy upload | Difficulty: medium | Cost: $20/mo | Leverage: Flips information asymmetry." - resource_type: knowledge - sort_order: 29 - # Learning Acceleration - - name: Socratic AI Tutor - description: "Set Claude as Socratic tutor: never give answers directly, ask leading questions, explain the mental model after you reach the answer." - detail: "Domain: Learning Acceleration | Tool: Claude with system prompt | Difficulty: easy | Cost: $20/mo | Leverage: 2-3x retention vs passive reading." - resource_type: knowledge - sort_order: 30 - - name: Personalized Curriculum Design - description: "Give Claude current level, target, hours/week, learning style. Get 90-day curriculum with weekly milestones, resources, and monthly applied projects." - detail: "Domain: Learning Acceleration | Tool: Claude + roadmap.sh | Difficulty: easy | Cost: $20/mo | Leverage: Eliminates tutorial hell." - resource_type: knowledge - sort_order: 31 - - name: Book & Content Compression - description: "Upload a book. Get core ideas, behavioral changes, strongest counter-argument, Anki flashcards. Read fully only if the compression hooks you." - detail: "Domain: Learning Acceleration | Tool: Claude (200k context) + PDF upload | Difficulty: easy | Cost: $20/mo | Leverage: 80% of value in 5 minutes." - resource_type: knowledge - sort_order: 32 - - name: Spaced Repetition Card Generation - description: "After learning anything, Claude generates 10-15 Anki cards following the 20 rules of formulating knowledge. 15 min creation + 10 min daily review." - detail: "Domain: Learning Acceleration | Tool: Claude + Anki | Difficulty: medium | Cost: $20/mo | Leverage: Highest-ROI learning technique in cognitive science." - resource_type: knowledge - sort_order: 33 - - name: Cross-Domain Concept Translation - description: "Ask Claude to explain X as if you're an expert in Y. Cross-domain analogies form deep understanding instantly. The Feynman method, automated." - detail: "Domain: Learning Acceleration | Tool: Claude with analogy generation | Difficulty: easy | Cost: $20/mo | Leverage: 3-5x faster understanding." - resource_type: knowledge - sort_order: 34 - # Module 48 - Model Recruitment - - name: Paris Model Scouting Pipeline - description: "Define casting criteria. AI drafts personalized outreach DMs. Batch scrape agency rosters + Instagram, score against criteria, generate outreach for top 20." - detail: "Domain: Module 48 - Model Recruitment | Tool: Claude + Instagram API + Make.com | Difficulty: medium | Cost: $20/mo | Leverage: Full week compressed to 2-3 hours." - resource_type: knowledge - sort_order: 35 - - name: International Model Outreach (Non-Paris) - description: "For London, Berlin, Milan, NYC, Seoul. Research local agencies, open casting calls. Generate city-specific outreach with adapted tone per market." - detail: "Domain: Module 48 - Model Recruitment | Tool: Claude + Instagram + Model Mayhem | Difficulty: hard | Cost: $20/mo | Leverage: Months of networking compressed to days per city." - resource_type: knowledge - sort_order: 36 - - name: Casting Brief Generator - description: "Describe concept, mood, setting. Claude generates professional casting brief with measurements, aesthetic references, wardrobe, logistics, usage rights, day rate context." - detail: "Domain: Module 48 - Model Recruitment | Tool: Claude + Midjourney | Difficulty: easy | Cost: $20 + $10/mo | Leverage: Attracts better talent, prevents scope creep." - resource_type: knowledge - sort_order: 37 - - name: TFP / Collaboration Negotiation Scripts - description: "AI drafts TFP or reduced-rate collaboration proposals that feel fair. Scripts for approaching models, responding to rate quotes, setting usage boundaries." - detail: "Domain: Module 48 - Model Recruitment | Tool: Claude with fashion industry context | Difficulty: easy | Cost: $20/mo | Leverage: Look experienced from your first shoot." - resource_type: knowledge - sort_order: 38 - # Module 48 - Pre-Launch & Organic Growth - - name: Pre-Launch Hype Sequence - description: "AI designs 6-week pre-launch content calendar: tease, build, buzz, launch. Claude writes every caption, CTA, and email. Schedule and execute on autopilot." - detail: "Domain: Module 48 - Pre-Launch | Tool: Claude + Typefully + Canva/Figma | Difficulty: medium | Cost: $20/mo | Leverage: Launch to demand, not into a void." - resource_type: knowledge - sort_order: 39 - - name: Organic Instagram Growth Engine - description: "AI analyzes top-performing posts, reverse-engineers patterns. Generates weekly plan: feed posts, Stories, Reels. Key: 10 genuine comments/day on adjacent accounts." - detail: "Domain: Module 48 - Pre-Launch | Tool: Claude + Later/Planoly + Insights | Difficulty: medium | Cost: $20/mo | Leverage: 200-500 new followers/week organically." - resource_type: knowledge - sort_order: 40 - - name: UGC & Micro-Influencer Outreach - description: "Identify micro-influencers (1k-20k) by analyzing hashtags, engagement, demographics. Generate personalized gifting proposals. Scale to 20-30 per month." - detail: "Domain: Module 48 - Pre-Launch | Tool: Claude + Instagram + Notion CRM | Difficulty: medium | Cost: $20/mo + product | Leverage: 3-5x better conversion than macro." - resource_type: knowledge - sort_order: 41 - - name: Email List Building & Nurture - description: "AI designs landing page copy, 5-email welcome sequence, weekly newsletter template. All in brand voice. Each email has one CTA." - detail: "Domain: Module 48 - Pre-Launch | Tool: Claude + Klaviyo/Mailchimp | Difficulty: medium | Cost: $20/mo + $0-20/mo | Leverage: Email converts 5-10x higher than social." - resource_type: knowledge - sort_order: 42 - - name: Supplier & Manufacturer Research - description: "AI researches manufacturers by product type and MOQ across regions. Drafts professional inquiry emails. Compares quotes with scoring matrix." - detail: "Domain: Module 48 - Pre-Launch | Tool: ChatGPT browsing + Claude | Difficulty: hard | Cost: $20/mo | Leverage: Research 30, contact 15, compare 8." - resource_type: knowledge - sort_order: 43 - - name: Trend & Competitor Intelligence - description: "Weekly AI scan: competitor launches, trending silhouettes/colors, trade show trends, pricing analysis. Claude synthesizes into 1-page actionable brief." - detail: "Domain: Module 48 - Pre-Launch | Tool: ChatGPT browsing + Claude | Difficulty: medium | Cost: $20/mo | Leverage: 70% of WGSN signal for $20/month." - resource_type: knowledge - sort_order: 44 - # Freelance Business - - name: Client Research & Proposal Generator - description: "Paste client's website, blog posts, job listings, tech stack. Claude generates tailored proposal with problem analysis, skill mapping, 3 pricing options." - detail: "Domain: Freelance Business | Tool: Claude + company website + LinkedIn | Difficulty: easy | Cost: $20/mo | Leverage: 25-40% close rate vs 5-10% generic." - resource_type: knowledge - sort_order: 45 - - name: Rate Benchmarking & Negotiation - description: "AI researches market rates for your exact service stack. Builds data-backed rate card with justification language." - detail: "Domain: Freelance Business | Tool: ChatGPT browsing + Claude | Difficulty: easy | Cost: $20/mo | Leverage: Most freelancers underprice by 20-40%." - resource_type: knowledge - sort_order: 46 - - name: Automated Admin & Invoicing - description: "AI designs freelance OS: project tracker, contract templates, automated invoice reminders, time tracking summaries. Claude writes SOWs from 3-line briefs." - detail: "Domain: Freelance Business | Tool: Claude + Notion/Airtable + Make.com | Difficulty: hard | Cost: $20 + $10-20/mo | Leverage: Reclaim 8-12 hours/week." - resource_type: knowledge - sort_order: 47 - - name: Portfolio & Case Study Writer - description: "For each project: what they needed, what you built, what the outcome was. Claude generates structured case study: challenge, approach, result, tech, client quote." - detail: "Domain: Freelance Business | Tool: Claude + your project data | Difficulty: easy | Cost: $20/mo | Leverage: Portfolio with case studies converts 3x better." - resource_type: knowledge - sort_order: 48 - - name: Cold Outreach at Scale - description: "Define ICP. AI identifies 100 companies/month via Apollo.io. Claude generates personalized cold emails referencing funding, product issues, job gaps. 3-email sequence." - detail: "Domain: Freelance Business | Tool: Claude + Apollo.io + Lemlist | Difficulty: hard | Cost: $20 + $50-100/mo | Leverage: 5 new conversations/month." - resource_type: knowledge - sort_order: 49 - # Language Learning (Daily) - - name: AI Conversation Partner - description: "Daily 15-minute conversation in target language. System prompt corrects grammar in-line, gives 3 repeated mistakes and 5 new vocab at session end." - detail: "Domain: Language Learning | Tool: ChatGPT Voice Mode or Claude | Difficulty: easy | Cost: $20/mo | Leverage: $30-50/hour tutor for $20/month unlimited." - resource_type: knowledge - sort_order: 50 - - name: Contextual Vocabulary Builder - description: "Tell Claude what you'll be doing this week. Get 30-40 situation-specific vocabulary words with example sentences. Export to Anki." - detail: "Domain: Language Learning | Tool: Claude + Anki | Difficulty: easy | Cost: $20/mo | Leverage: 4-5x better retention than word lists." - resource_type: knowledge - sort_order: 51 - - name: Immersion Content Curator - description: "AI curates daily immersion diet matched to your level and interests: YouTube channel, podcast, social accounts, news source in target language." - detail: "Domain: Language Learning | Tool: ChatGPT browsing + Claude | Difficulty: easy | Cost: $20/mo | Leverage: Removes the discovery problem." - resource_type: knowledge - sort_order: 52 - - name: Writing Correction Loop - description: "Write daily 100-200 word journal entry in target language. Claude returns corrected version, grammar explanations, native rephrasing, one advanced structure." - detail: "Domain: Language Learning | Tool: Claude with writing analysis | Difficulty: easy | Cost: $20/mo | Leverage: Pattern recognition for your specific errors." - resource_type: knowledge - sort_order: 53 - - name: Grammar Pattern Drilling - description: "Tell Claude which structure you struggle with. It explains with 3 examples, gives 10 fill-in exercises of increasing difficulty, corrects, repeats until 8/10 right." - detail: "Domain: Language Learning | Tool: Claude Socratic mode | Difficulty: easy | Cost: $20/mo | Leverage: Targeted drilling on YOUR weak points." - resource_type: knowledge - sort_order: 54 - # Travel & Lifestyle Optimization - - name: Flight & Hotel Deal Hunting - description: "Tell Claude destination, dates, budget. Research cheapest booking window, best travel day, nearby airports, hotel vs Airbnb, credit card points strategies." - detail: "Domain: Travel & Lifestyle | Tool: ChatGPT browsing + Google Flights + Kayak | Difficulty: easy | Cost: $20/mo | Leverage: 20-40% savings, $1,000-3,000/year." - resource_type: knowledge - sort_order: 55 - - name: City & Experience Optimizer - description: "Give Claude city, interests, budget, days. Get day-by-day itinerary optimized for geographic clustering, local favorites over tourist traps, reservation timing." - detail: "Domain: Travel & Lifestyle | Tool: Claude + Google Maps + local blogs | Difficulty: easy | Cost: $20/mo | Leverage: Local knowledge that would take 3 trips." - resource_type: knowledge - sort_order: 56 - - name: Relocation Research Assistant - description: "Claude compares cities across cost of living, tax implications, visa, quality of life, internet, coworking, social scene, healthcare. Weighted scorecard based on YOUR priorities." - detail: "Domain: Travel & Lifestyle | Tool: Claude + Numbeo + local forums | Difficulty: medium | Cost: $20/mo | Leverage: Systematic comparison across 20+ factors." - resource_type: knowledge - sort_order: 57 diff --git a/curriculum/tracks/embodied-ai.yaml b/curriculum/tracks/embodied-ai.yaml deleted file mode 100644 index 540a740d..00000000 --- a/curriculum/tracks/embodied-ai.yaml +++ /dev/null @@ -1,428 +0,0 @@ -id: embodied-ai -title: Embodied AI -description: > - Explore the full landscape of AI in the physical world - world models, robot - foundation models, humanoid robotics, service robots, autonomous vehicles, - agentic automation, edge inference, and the physical AI industry. -difficulty: intermediate -track_type: resource -category: specialization -category_order: 4 -track_order: 1 -modules: - - id: world_models - title: World Models - objective: > - How AI learns to predict and plan in the physical world - not just process text. - color: "#55cdff" - sort_order: 1 - resources: - - name: Yann LeCun - AMI Labs (Meta) - url: https://ai.meta.com/blog/v-jepa-yann-lecun-ai-model-video-joint-embedding-predictive-architecture/ - description: > - JEPA architecture for learning world models through latent prediction rather - than pixel generation. - detail: > - Study the theoretical foundation: why LeCun argues world models, not - autoregressive token prediction, are the path to AMI. - sort_order: 1 - - name: World Labs (Fei-Fei Li) - url: https://www.worldlabs.ai - description: > - Spatial intelligence startup building large world models for 3D scene - understanding and generation. - detail: > - Track the spatial AI thesis and how it differs from language-first - approaches. - sort_order: 2 - - name: NVIDIA Cosmos - url: https://developer.nvidia.com/cosmos - description: > - World foundation models for physical AI - video generation, driving - simulation, and robotics planning. - detail: > - Study how Cosmos bridges simulation and real-world prediction for embodied - systems. - sort_order: 3 - - name: Google DeepMind Genie 2 - url: https://deepmind.google/discover/blog/genie-2-a-large-scale-foundation-world-model/ - description: > - Foundation world model generating interactive 3D environments from single - images. - detail: > - Understand action-conditioned generation and how it enables training - embodied agents in imagination. - sort_order: 4 - - name: DreamerV3 - url: https://danijar.com/project/dreamerv3/ - description: > - Model-based RL agent that learns a world model and achieves human-level play - across diverse domains. - detail: > - The canonical reference for world-model-based reinforcement learning in - embodied settings. - sort_order: 5 - - - id: core - title: Embodied AI - objective: > - From simulation to real robots - physical reasoning, manipulation, and - navigation. - color: "#ffc47c" - sort_order: 2 - resources: - - name: Google DeepMind Robotics - url: https://deepmind.google/discover/blog/?category=robotics - description: > - RT-2, RT-X, and foundation models for robot control via language grounding. - detail: > - Track how vision-language-action models are closing the sim-to-real gap. - sort_order: 1 - - name: NVIDIA Isaac Sim - url: https://developer.nvidia.com/isaac-sim - description: > - GPU-accelerated simulation for training robot policies at scale before - physical deployment. - detail: > - Study sim-to-real transfer pipelines and domain randomization. - sort_order: 2 - - name: Open X-Embodiment - url: https://robotics-transformer-x.github.io - description: > - Cross-embodiment dataset and models enabling knowledge transfer across - different robot platforms. - detail: > - Understand how shared datasets unlock generalization across robot - morphologies. - sort_order: 3 - - name: Hugging Face LeRobot - url: https://github.com/huggingface/lerobot - description: > - Open-source toolkit for real-world robotics with pretrained policies and - datasets. - detail: > - Hands-on practice with imitation learning and policy fine-tuning. - sort_order: 4 - - name: Physical Intelligence (pi) - url: https://www.physicalintelligence.company - description: > - Building general-purpose robot foundation models trained on diverse physical - tasks. - detail: > - Watch for breakthroughs in generalist manipulation policies. - sort_order: 5 - - - id: humanoid - title: Humanoid Robotics - objective: > - The race to build robots that work alongside people - Atlas, Optimus, Figure, - and more. - color: "#5bb86e" - sort_order: 3 - resources: - - name: Figure AI - url: https://www.figure.ai - description: > - Figure 02 humanoid with OpenAI partnership for conversational reasoning and - autonomous task execution. - detail: > - Study the LLM-to-actuator pipeline and BMW factory deployment. - sort_order: 1 - - name: Tesla Optimus - url: https://www.tesla.com/optimus - description: > - Leveraging Tesla FSD neural nets and Dojo for humanoid robot perception and - control. - detail: > - Track the vertical integration play: AI chips, data, and manufacturing at - scale. - sort_order: 2 - - name: Boston Dynamics Atlas - url: https://bostondynamics.com/atlas - description: > - Electric Atlas with advanced manipulation, whole-body control, and industrial - applications. - detail: > - Study the transition from hydraulic research platform to commercial electric - humanoid. - sort_order: 3 - - name: 1X Technologies - url: https://www.1x.tech - description: > - NEO humanoid designed for home environments with learned behaviors from - neural network policies. - detail: > - Watch the consumer humanoid market thesis and safety approach. - sort_order: 4 - - name: Agility Robotics Digit - url: https://agilityrobotics.com - description: > - Purpose-built for warehouse logistics with Amazon deployment partnership. - detail: > - Study the focused use-case strategy vs general-purpose humanoids. - sort_order: 5 - - name: Unitree - url: https://www.unitree.com - description: > - Low-cost humanoid and quadruped robots making embodied AI accessible for - research and deployment. - detail: > - Track the cost disruption angle: $16K humanoid vs $100K+ competitors. - sort_order: 6 - - - id: service - title: Service Robotics - objective: > - Robots already at work - restaurants, hospitals, warehouses, and last-mile - delivery. - color: "#eb5757" - sort_order: 4 - resources: - - name: Bear Robotics Servi - url: https://www.bearrobotics.ai - description: > - Autonomous food service robots deployed in restaurants, hotels, and casinos. - detail: > - Study the unit economics of robot waitstaff vs human labor costs. - sort_order: 1 - - name: Savioke Relay - url: https://www.savioke.com - description: > - Autonomous delivery robots for hotels, hospitals, and high-rises. - detail: > - Track the hospitality automation ROI and guest experience data. - sort_order: 2 - - name: Nuro - url: https://www.nuro.ai - description: > - Purpose-built autonomous delivery vehicles for groceries, food, and packages. - detail: > - Study the regulatory path for driverless delivery on public roads. - sort_order: 3 - - name: Serve Robotics - url: https://www.serverobotics.com - description: > - Sidewalk delivery robots partnered with Uber Eats for last-mile autonomous - delivery. - detail: > - Track the Uber partnership and sidewalk autonomy regulatory landscape. - sort_order: 4 - - name: Diligent Robotics Moxi - url: https://www.diligentrobots.com - description: > - Hospital service robot handling supply delivery, reducing nurse walking time - by 30%. - detail: > - Study healthcare labor augmentation and clinical workflow integration. - sort_order: 5 - - - id: autonomous - title: Autonomous Systems - objective: > - Vehicles and drones that navigate the real world without a human at the wheel. - color: "#5e6ad2" - sort_order: 5 - resources: - - name: Waymo - url: https://waymo.com - description: > - Fully autonomous ride-hailing in San Francisco, Phoenix, LA. Most mature - commercial self-driving. - detail: > - Study the sensor fusion stack and how they achieved commercial scale. - sort_order: 1 - - name: Tesla FSD - url: https://www.tesla.com/autopilot - description: > - Vision-only self-driving using end-to-end neural networks trained on fleet - data. - detail: > - Track the camera-only vs lidar debate and real-world safety data. - sort_order: 2 - - name: Cruise - url: https://www.getcruise.com - description: > - GM-backed autonomous vehicles with urban robotaxi operations. - detail: > - Study the regulatory setbacks and recovery strategy post-2023 incidents. - sort_order: 3 - - name: Skydio - url: https://www.skydio.com - description: > - Autonomous drones for infrastructure inspection, public safety, and defense. - detail: > - Study the enterprise drone autonomy stack and computer vision pipeline. - sort_order: 4 - - name: Zipline - url: https://www.flyzipline.com - description: > - Autonomous drone delivery for medical supplies and consumer goods at national - scale. - detail: > - Track the longest-running commercial drone delivery operation globally. - sort_order: 5 - - - id: agentic - title: Agentic Automation - objective: > - AI doing the work of knowledge workers - customer service, back-office, and - beyond. - color: "#f472b6" - sort_order: 6 - resources: - - name: Sierra AI - url: https://sierra.ai - description: > - Enterprise AI agents for customer service replacing traditional contact - centers. Founded by Bret Taylor. - detail: > - Study the conversational AI agent architecture for enterprise support. - sort_order: 1 - - name: Decagon - url: https://decagon.ai - description: > - AI customer support agents with enterprise-grade reliability and compliance. - detail: > - Track how AI agents handle edge cases, escalation, and knowledge grounding. - sort_order: 2 - - name: UiPath - url: https://www.uipath.com - description: > - RPA platform evolving into AI-powered agentic automation for enterprise - workflows. - detail: > - Study the RPA-to-AI-agent evolution and enterprise adoption. - sort_order: 3 - - name: Anthropic Computer Use - url: https://docs.anthropic.com/en/docs/agents-and-tools/computer-use - description: > - Claude operating computers via screenshots and mouse/keyboard - - general-purpose digital agent. - detail: > - Study the computer-use paradigm: how LLMs interact with arbitrary software. - sort_order: 4 - - name: Cognition Devin - url: https://www.cognition.ai - description: > - Autonomous AI software engineer handling full development tasks end-to-end. - detail: > - Track the software engineering agent benchmark and real-world adoption. - sort_order: 5 - - name: Adept AI - url: https://www.adept.ai - description: > - AI agents that interact with enterprise software through natural language - commands. - detail: > - Study the action model approach: training AI to use software like humans do. - sort_order: 6 - - - id: edge_inference - title: Edge Inference - objective: > - Run models on tiny hardware at real-time speed - the last mile from training - to deployment. - color: "#4ade80" - sort_order: 7 - resources: - - name: NVIDIA TensorRT - url: https://developer.nvidia.com/tensorrt - description: > - High-performance deep learning inference optimizer and runtime for NVIDIA - GPUs - the standard for production edge deployment. - detail: > - Learn the full pipeline: PyTorch to ONNX to TensorRT engine. Master INT8 - calibration and layer fusion. - sort_order: 1 - - name: ONNX Runtime - url: https://onnxruntime.ai - description: > - Cross-platform inference engine supporting CPU, GPU, and NPU hardware. The - universal intermediate format for model deployment. - detail: > - Use as the portable inference layer: export once to ONNX, deploy anywhere - (Jetson, x86, ARM, browser). - sort_order: 2 - - name: NVIDIA Jetson Orin - url: https://developer.nvidia.com/embedded/jetson-orin - description: > - Edge AI computer for robotics - up to 275 TOPS in 60W. The standard - hardware for on-device robot inference. - detail: > - Target platform for proof projects. Orin Nano ($250) for entry, Orin NX for - production robots. - sort_order: 3 - - name: Qualcomm AI Hub - url: https://aihub.qualcomm.com - description: > - Optimize and deploy models on Qualcomm Snapdragon chips - phones, XR - headsets, IoT devices, and drones. - detail: > - Study the mobile/drone edge inference stack. Relevant for autonomous drones - and wearable AI. - sort_order: 4 - - name: Apache TVM - url: https://tvm.apache.org - description: > - Open-source compiler framework that optimizes ML models for any hardware - backend (CPU, GPU, FPGA, custom accelerators). - detail: > - Deep dive into how model compilation works across hardware targets. Advanced - but fundamental knowledge. - sort_order: 5 - - name: ExecuTorch (Meta) - url: https://pytorch.org/executorch - description: > - PyTorch's on-device inference framework - deploy PyTorch models to mobile, - embedded, and edge with minimal changes. - detail: > - The PyTorch-native path to edge: no ONNX export needed. Watch for robotics - adoption alongside LeRobot. - sort_order: 6 - - - id: physical-ai - title: Physical AI - objective: > - Prep for the next wave of AI roles - world models, sim-to-real, 3D vision, - and physical AI. - color: "#ffc47c" - sort_order: 8 - resources: - - name: NVIDIA Physical AI - url: https://developer.nvidia.com/physical-ai - description: > - NVIDIA's physical AI platform: Isaac Sim, Cosmos, and the full sim-to-real - stack. - detail: > - Study the end-to-end pipeline from simulation to real-world robot deployment. - sort_order: 1 - - name: World Labs - url: https://www.worldlabs.ai - description: > - Fei-Fei Li's spatial intelligence company building large world models. - detail: > - Track the spatial AI thesis and job openings in this space. - sort_order: 2 - - name: Figure AI Careers - url: https://www.figure.ai/careers - description: > - Humanoid robotics company hiring for world model, perception, and control - roles. - detail: > - Study their job descriptions to understand the skill requirements for - physical AI roles. - sort_order: 3 - - name: Physical Intelligence (pi) - url: https://www.physicalintelligence.company - description: > - General-purpose robot foundation models. Raised $400M+. - detail: > - Track the company building the GPT moment for robotics. - sort_order: 4 - - name: 1X Technologies - url: https://www.1x.tech - description: > - NEO humanoid built with learned neural network policies. - detail: > - Study the consumer humanoid thesis and their ML-first approach. - sort_order: 5 diff --git a/curriculum/tracks/freelance-strategy.yaml b/curriculum/tracks/freelance-strategy.yaml deleted file mode 100644 index fad3c36d..00000000 --- a/curriculum/tracks/freelance-strategy.yaml +++ /dev/null @@ -1,159 +0,0 @@ -id: freelance-strategy -title: Freelance Strategy -description: > - Build a sustainable freelance practice - positioning, pricing, and growth. -difficulty: intermediate -track_type: resource -category: career -category_order: 5 -track_order: 2 -modules: - - id: realtime_systems - title: Real-Time Systems - objective: > - Build AI products that feel instant - streaming UX, event pipelines, and low-latency infra. - color: "#55cdff" - sort_order: 1 - resources: - - name: LiveKit - url: https://livekit.io - description: Real-time transport infrastructure for audio, video, and data channels used in interactive AI products. - detail: Useful when you need low-latency sessions. Voice is one use case, not the only one. - sort_order: 1 - - name: Ably - url: https://ably.com/docs - description: Managed pub/sub and WebSocket infrastructure with global edge fan-out and delivery guarantees. - detail: Great for shipping real-time updates fast without running custom socket infrastructure. - sort_order: 2 - - name: NATS - url: https://docs.nats.io - description: Lightweight event bus for low-latency messaging, request/reply, and streaming via JetStream. - detail: Strong choice for control planes, internal signaling, and event-driven AI workers. - sort_order: 3 - - name: Apache Kafka - url: https://kafka.apache.org/documentation/ - description: Durable event streaming for high-throughput pipelines, replayable logs, and consumer groups. - detail: Best when you need auditable event history and independent downstream consumers. - sort_order: 4 - - name: MDN WebSocket API - url: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API - description: Core browser/server bidirectional transport for live UX, streaming updates, and control messages. - detail: Know heartbeats, reconnect strategy, auth refresh, and backpressure handling. - sort_order: 5 - - name: Cloudflare Durable Objects - url: https://developers.cloudflare.com/durable-objects/ - description: Stateful edge compute for room/session coordination and shared low-latency state. - detail: Useful for collaborative AI, session routing, and deterministic per-session state handling. - sort_order: 6 - - - id: apis_at_scale - title: APIs at Scale - objective: > - Design APIs that handle real traffic - rate limiting, caching, observability, and scaling. - color: "#ffc47c" - sort_order: 2 - resources: - - name: Kong Gateway - url: https://docs.konghq.com - description: "API gateway: rate limiting, auth, load balancing, observability plugins, and service mesh integration." - detail: Know gateway patterns cold. Most production APIs sit behind Kong, Envoy, or AWS API Gateway. - sort_order: 1 - - name: gRPC Documentation - url: https://grpc.io/docs/ - description: "High-performance RPC framework: protobuf, streaming, deadlines, and service-to-service communication." - detail: gRPC is the default for internal APIs at scale. Practice protobuf schemas and bidirectional streaming. - sort_order: 2 - - name: Designing Data-Intensive Applications - url: https://dataintensive.net - description: "The bible for distributed systems: replication, partitioning, consistency, and batch/stream processing." - detail: Read chapters on replication and partitioning. Every system design interview draws from this book. - sort_order: 3 - - name: FastAPI - url: https://fastapi.tiangolo.com - description: "Modern Python API framework: async, type hints, auto-docs, WebSocket support, and dependency injection." - detail: The go-to for Python ML/AI APIs. Know how to structure a production FastAPI service with middleware. - sort_order: 4 - - name: Hono - url: https://hono.dev - description: "Ultra-fast edge-first web framework: works on Cloudflare Workers, Deno, Bun, and Node. TypeScript-native." - detail: Increasingly used for high-performance TypeScript APIs. Study the middleware and edge deployment patterns. - sort_order: 5 - - - id: ai_agent_infra - title: AI Agent Infra - objective: > - Ship AI agents that work at scale - orchestration, guardrails, cost control, and deployment. - color: "#5bb86e" - sort_order: 3 - resources: - - name: LangGraph - url: https://langchain-ai.github.io/langgraph/ - description: "Stateful agent orchestration: graph-based workflows, checkpointing, human-in-the-loop, and multi-agent coordination." - detail: The leading framework for production agent architectures. Study graph topologies and state management. - sort_order: 1 - - name: CrewAI - url: https://docs.crewai.com - description: Multi-agent orchestration with role-based agents, task delegation, and process management. - detail: Simpler than LangGraph for multi-agent setups. Study the role/task/process abstraction. - sort_order: 2 - - name: Anthropic Tool Use Docs - url: https://docs.anthropic.com/en/docs/build-with-claude/tool-use - description: "Claude's native tool calling: function definitions, structured outputs, and multi-turn tool use patterns." - detail: Master tool-use patterns. This is the core primitive for every AI agent. - sort_order: 3 - - name: OpenAI Assistants API - url: https://platform.openai.com/docs/assistants/overview - description: "Managed agent infrastructure: threads, runs, tools, file search, and code interpreter." - detail: Understand what managed agent APIs abstract vs what you'd build custom at a startup. - sort_order: 4 - - name: Letta (MemGPT) - url: https://docs.letta.com - description: Stateful agent framework with persistent memory, tool execution, and long-running agent deployment. - detail: Study the memory architecture - long-term memory is the hardest unsolved problem in production agents. - sort_order: 5 - - name: Temporal - url: https://temporal.io - description: "Durable execution engine: workflow orchestration, retries, timeouts, and exactly-once execution guarantees." - detail: Not AI-specific but critical for production agents. Temporal solves the reliability layer that agents need. - sort_order: 6 - - - id: production_hardening - title: Production Hardening - objective: > - Make your system survive production - observability, deployments, incidents, and chaos testing. - color: "#eb5757" - sort_order: 4 - resources: - - name: Grafana + Prometheus Stack - url: https://grafana.com/docs/grafana/latest/ - description: "Open-source observability: metrics (Prometheus), logs (Loki), traces (Tempo), and dashboards (Grafana)." - detail: The default observability stack. Know how to instrument a service and build actionable dashboards. - sort_order: 1 - - name: OpenTelemetry - url: https://opentelemetry.io/docs/ - description: "Vendor-neutral telemetry standard: traces, metrics, and logs with auto-instrumentation for major frameworks." - detail: OTel is the standard. Learn to instrument Python and TypeScript services with spans and custom metrics. - sort_order: 2 - - name: Google SRE Books - url: https://sre.google/books/ - description: "Site Reliability Engineering canon: SLOs, error budgets, toil reduction, incident response, and on-call practices." - detail: Read chapters on SLOs, monitoring, and incident management. These concepts come up in every production role. - sort_order: 3 - - name: k6 Load Testing - url: https://k6.io/docs/ - description: "Modern load testing: scripted scenarios, thresholds, CI integration, and distributed testing." - detail: Know how to load test an API before launch. Practice writing k6 scripts with realistic traffic patterns. - sort_order: 4 - - name: Fly.io / Railway - url: https://fly.io/docs/ - description: "Edge deployment platforms: containers, global distribution, auto-scaling, and production Postgres." - detail: Know how to deploy and scale services globally. Practice zero-downtime deployments. - sort_order: 5 - - - id: positioning - title: Positioning - objective: > - Become the obvious hire - proof projects, outreach strategy, and what signals top 0.01%. - color: "#5e6ad2" - sort_order: 5 - resources: [] diff --git a/curriculum/tracks/frontend-engineering.yaml b/curriculum/tracks/frontend-engineering.yaml index e5920b31..a08e296b 100644 --- a/curriculum/tracks/frontend-engineering.yaml +++ b/curriculum/tracks/frontend-engineering.yaml @@ -7,7 +7,7 @@ difficulty: intermediate track_type: resource category: systems category_order: 2 -track_order: 5 +track_order: 6 modules: - id: state title: State Management diff --git a/curriculum/tracks/gpu-for-ai.yaml b/curriculum/tracks/gpu-for-ai.yaml index 00c5ae64..d0943277 100644 --- a/curriculum/tracks/gpu-for-ai.yaml +++ b/curriculum/tracks/gpu-for-ai.yaml @@ -8,7 +8,7 @@ difficulty: intermediate track_type: resource category: systems category_order: 2 -track_order: 4 +track_order: 5 modules: - id: architecture title: GPU Architecture diff --git a/curriculum/tracks/interview-prep.yaml b/curriculum/tracks/interview-prep.yaml index d1e50dba..c9a5fc2b 100644 --- a/curriculum/tracks/interview-prep.yaml +++ b/curriculum/tracks/interview-prep.yaml @@ -5,7 +5,7 @@ description: > difficulty: intermediate track_type: resource category: career -category_order: 5 +category_order: 4 track_order: 1 modules: - id: faang diff --git a/curriculum/tracks/practical-ml.yaml b/curriculum/tracks/practical-ml.yaml index 405f8446..f8957615 100644 --- a/curriculum/tracks/practical-ml.yaml +++ b/curriculum/tracks/practical-ml.yaml @@ -1,8 +1,8 @@ id: practical-ml -title: Practical ML Systems +title: Practical ML description: > - How production ML systems actually work - recommendations, ranking, - retrieval, feedback loops, and evaluation beyond accuracy. + How ML models represent, score, and evaluate - embeddings, ranking + paradigms, recommender algorithms, feedback loops, and metrics. difficulty: intermediate track_type: concept is_published: true @@ -32,12 +32,12 @@ modules: - concept_id: cold-start-problem sort_order: 5 - # ── Module 2: Retrieval Systems ─────────────────────────────────────── - - id: retrieval-systems - title: Retrieval Systems + # ── Module 2: Representation & Retrieval ────────────────────────────── + - id: retrieval-primitives + title: Representation & Retrieval objective: > - Build intuition for approximate nearest neighbor search, two-tower - models, and the retrieval-then-rank pipeline used in modern systems. + Understand how embeddings encode similarity and how approximate + nearest neighbor search and dual-encoder models retrieve candidates. estimated_time_minutes: 45 sort_order: 2 color: "#4ade80" @@ -50,15 +50,13 @@ modules: sort_order: 3 - concept_id: two-tower-model sort_order: 4 - - concept_id: retrieval-then-rank - sort_order: 5 - # ── Module 3: Ranking Systems ───────────────────────────────────────── - - id: ranking-systems - title: Ranking Systems + # ── Module 3: Ranking Paradigms ─────────────────────────────────────── + - id: ranking-paradigms + title: Ranking Paradigms objective: > - Learn how search engines and feeds order results using pointwise, - pairwise, and listwise learning-to-rank approaches. + Compare pointwise, pairwise, and listwise approaches to learning + relevance scores, and measure ranking quality with NDCG. estimated_time_minutes: 60 sort_order: 3 color: "#ffc47c" @@ -95,8 +93,8 @@ modules: - id: evaluation-metrics title: Evaluation Metrics objective: > - Go beyond accuracy to measure what matters in production - precision, - recall, calibration, and online experiment design. + Go beyond accuracy to measure model quality - precision, recall, + calibration, and the gap between offline metrics and online outcomes. estimated_time_minutes: 45 sort_order: 5 color: "#818cf8" diff --git a/frontend/src/components/AddSourceModal.tsx b/frontend/src/components/AddSourceModal.tsx index 807a80f2..323c4252 100644 --- a/frontend/src/components/AddSourceModal.tsx +++ b/frontend/src/components/AddSourceModal.tsx @@ -5,9 +5,7 @@ import { motion, AnimatePresence } from "motion/react"; import { API_URL, WS_URL } from "../config"; import { ARTICLE_STEPS, YOUTUBE_STEPS } from "../lib/ingest-constants"; import { api } from "../lib/api/endpoints"; -import type { PatternImpactPreview } from "../types"; import { Button } from "./ui/Button"; -import { PatternImpactPanel } from "./signal/PatternImpactPanel"; import { toast } from "./ui/toast"; interface AddSourceModalProps { @@ -214,9 +212,6 @@ export function AddSourceModal({ const [error, set_error] = useState(null); const [selected_file, set_selected_file] = useState(null); const [is_dragging, set_is_dragging] = useState(false); - const [pattern_preview, set_pattern_preview] = - useState(null); - const [preview_loading, set_preview_loading] = useState(false); const ws_ref = useRef(null); const input_ref = useRef(null); const file_input_ref = useRef(null); @@ -230,18 +225,25 @@ export function AddSourceModal({ const enterprise_img_ref = useRef(null); const enterprise_thumb_ref = useRef(null); + // Job post mode + const [job_mode, set_job_mode] = useState(false); + const [job_text, set_job_text] = useState(""); + const [job_company, set_job_company] = useState(""); + useEffect(() => { if (is_open) { set_url(""); set_job(null); set_error(null); set_selected_file(null); - set_pattern_preview(null); - set_preview_loading(false); + set_enterprise_mode(false); set_enterprise_text(""); set_enterprise_images([]); set_enterprise_thumbnail(null); + set_job_mode(false); + set_job_text(""); + set_job_company(""); setTimeout(() => input_ref.current?.focus(), 100); } }, [is_open]); @@ -293,26 +295,12 @@ export function AddSourceModal({ on_success(); toast({ variant: "success", - title: "Ingestion complete", + title: "Import complete", description: typeof msg.chunks === "number" ? `Added ${msg.chunks} chunks.` : undefined, }); - - // Fetch pattern impact preview for the completed job - set_preview_loading(true); - api - .getJobPatternPreview(job_id) - .then((preview) => { - if (preview.deltas.length > 0) { - set_pattern_preview(preview); - } - }) - .catch(() => { - // Pattern preview is non-critical - swallow errors - }) - .finally(() => set_preview_loading(false)); } else if (msg.type === "error") { set_job((prev) => prev ? { ...prev, status: "failed", error: msg.message } : null, @@ -320,7 +308,7 @@ export function AddSourceModal({ set_error(msg.message); toast({ variant: "error", - title: "Ingestion failed", + title: "Import failed", description: msg.message, }); } @@ -330,7 +318,7 @@ export function AddSourceModal({ set_error("WebSocket connection error"); toast({ variant: "error", - title: "Ingestion connection error", + title: "Import connection error", description: "WebSocket connection error.", }); }; @@ -441,6 +429,45 @@ export function AddSourceModal({ } }; + const start_job_ingest = async () => { + if (!job_text.trim()) return; + set_error(null); + + try { + const title = job_company ? `${job_company} - Job Post` : "Job Post"; + const payload: Record = { + content: job_text.trim(), + content_type: "job", + namespace: "global", + manual_title: title, + }; + if (url.trim()) { + payload.manual_description = url.trim(); + } + const response = await fetch(`${API_URL}/ingest`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error("Failed to start ingestion"); + } + + const data: IngestJob = await response.json(); + set_job(data); + connect_websocket(data.job_id); + } catch (err) { + const message = err instanceof Error ? err.message : "Unknown error"; + set_error(message); + toast({ + variant: "error", + title: "Job post ingestion failed", + description: message, + }); + } + }; + const handle_file_select = (file: File) => { const allowed = [".pdf", ".md", ".markdown", ".txt", ".text"]; const ext = file.name.toLowerCase().slice(file.name.lastIndexOf(".")); @@ -554,7 +581,10 @@ export function AddSourceModal({ onKeyDown={handle_key_down} placeholder="Paste YouTube URL or article link..." disabled={ - is_processing || is_completed || !!selected_file + is_processing || + is_completed || + !!selected_file || + job_mode } className="input-modern pr-20" /> @@ -566,30 +596,73 @@ export function AddSourceModal({ - {/* Enterprise toggle */} + {/* Mode toggles */} {!is_processing && !is_completed && ( - + > + + + + Enterprise project + + + + )} {/* Enterprise mode fields */} @@ -752,72 +825,124 @@ export function AddSourceModal({ )} - {/* Divider */} - {!is_processing && !is_completed && !enterprise_mode && ( -
-
- or drop a file -
+ {/* Job post mode fields */} + {job_mode && !is_processing && !is_completed && ( +
+