📁 Mentor — Source Code

An AI advisor for solo founders of service and expert businesses

📂 Project Structure

projects/mentor/ ├── mentor_server.py # Flask backend + retrieval + Claude API ├── mentor-ui.html # Full frontend (Daily, Wiki, Learn & Talk tabs) ├── demo.html # Portfolio live demo (browser-only) ├── power-of-process.txt # Sample knowledge base (demo) └── config.txt # User's API key (local only)

🧠 Core Logic — Retrieval + Grounding

Every chat request is processed through a two-layer context pipeline before being sent to Claude Haiku. Layer 1 is always-on reality (user profile, schedule, priorities, progress log). Layer 2 is keyword-retrieved wiki chunks relevant to the specific question.

Wiki Chunking

Every wiki markdown file is split by ## headings into teachable sections, stored in memory for instant lookup.

def load_wiki_chunks():
    chunks = []
    for md_file in WIKI_DIR.rglob("*.md"):
        topic = md_file.parent.name
        text = md_file.read_text(encoding="utf-8")
        sections = re.split(r"\n(?=## )", text)
        for sec in sections:
            if len(sec) < 100:
                continue
            title_match = re.match(r"##\s*(.+)", sec)
            title = title_match.group(1).strip() if title_match else topic
            chunks.append({"topic": topic, "title": title, "text": sec})
    return chunks

Keyword Retrieval

A simple tokenizer + set intersection score keeps the system fast and transparent. No vector database needed at this scale.

def retrieve(question, k=3):
    q_tokens = set(tokenize(question))
    scored = []
    for chunk in WIKI_CHUNKS:
        chunk_tokens = set(tokenize(chunk["text"]))
        score = len(q_tokens & chunk_tokens)
        title_tokens = set(tokenize(chunk["title"]))
        score += 2 * len(q_tokens & title_tokens)  # title boost
        if score > 0:
            scored.append((score, chunk))
    scored.sort(key=lambda x: x[0], reverse=True)
    return [c for _, c in scored[:k]]

🎯 System Prompt — The Output Contract

The strict rules enforced on every reply. This is what turns a chatbot into a mentor.

SYSTEM_PROMPT = """You are a unified-voice mentor for a solo founder
of a service and expert business.

CRITICAL RULES:
1. Reply in MAXIMUM 2 paragraphs. Each paragraph MAXIMUM 3 sentences.
2. Use direct, clear, simple language. No jargon. No filler.
3. Candid tone — tell the truth even when uncomfortable.
   Be encouraging when real progress has been made.
4. Never say "Baker says" or "Weiss recommends" —
   speak as one trusted advisor using "I".
5. Ground EVERY recommendation in the user's actual context
   (schedule, priorities, progress, skill level).
6. Use the wiki frameworks as the logic, but translate them
   to the user's real situation.
7. If the wiki doesn't cover it, say so in one sentence.
8. End with a specific next action when the question calls for one.
9. Never suggest something the user clearly lacks capacity for
   without naming that gap first.
"""

💬 Chat Endpoint With Session Memory

The chat endpoint accepts a short history array from the browser so follow-up questions work naturally. It also appends every exchange to a dated markdown log.

@app.route("/api/ask", methods=["POST"])
def api_ask():
    data = request.get_json(force=True)
    question = (data.get("question") or "").strip()
    history = data.get("history") or []

    retrieved = retrieve(question, k=3)
    wiki_context = "\n\n---\n\n".join(
        f"[{c['topic']} — {c['title']}]\n{c['text']}" for c in retrieved
    )
    current_user_message = f"""USER CONTEXT:
{LAYER1_CONTEXT}

---

RELEVANT WIKI FRAMEWORKS:
{wiki_context}

---

QUESTION: {question}"""

    messages = []
    for msg in history[-10:]:  # cap at 10
        role = msg.get("role")
        content = (msg.get("content") or "").strip()
        if role in ("user", "assistant") and content:
            messages.append({"role": role, "content": content})
    messages.append({"role": "user", "content": current_user_message})

    resp = anthropic_client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=400,
        system=SYSTEM_PROMPT,
        messages=messages
    )
    answer = resp.content[0].text
    append_chat_log(question, answer, retrieved)
    return jsonify({"answer": answer, "sources": retrieved})

📚 Live Demo Notes

The portfolio live demo (projects/mentor/demo.html) runs entirely in the browser with no backend. It loads a single sample knowledge base file (power-of-process.txt) and calls the Gemini API directly from the browser when the user provides an API key. This preserves the production UI (Daily, Wiki, Learn & Talk tabs) and the tone contract of the system prompt.

The full production version uses Claude Haiku 4.5 via the Anthropic SDK, a multi-file wiki, and the local Flask server for true reality grounding (schedule, priorities, progress logs).