An AI advisor for solo founders of service and expert businesses
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.
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
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]]
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. """
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})
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).