# Starter Repo

```
cerebros/
├─ README.md
├─ LICENSE
├─ .gitignore
├─ .env.example
├─ Makefile
├─ docker-compose.yml
├─ openapi/
│  └─ cerebro.v1.yaml
├─ schemas/
│  ├─ cerebro-package.schema.json        # JSON Schema for .cerebro.jsonld
│  └─ mcp-tools/                         # MCP + function-calling schemas
│     ├─ cerebro.query.json
│     ├─ cerebro.update.json
│     └─ cerebro.trace.json
├─ apps/
│  ├─ api/                               # FastAPI service (REST)
│  │  ├─ main.py
│  │  ├─ graph.py                        # Neo4j adapters
│  │  ├─ pedagogy.py                     # mastery/decay update
│  │  ├─ packages.py                     # import/validate .jsonld into DB
│  │  ├─ settings.py
│  │  └─ requirements.txt
│  └─ mcp/                               # optional MCP tool server
│     ├─ server.py
│     └─ requirements.txt
├─ db/
│  ├─ seed/
│  │  ├─ math.calculus.power_rule.jsonld # example brain package
│  │  └─ load.py                         # loads package(s) into Neo4j
│  └─ migrations/                        # Cypher files to set indexes/constraints
│     ├─ 001_init_constraints.cypher
│     └─ 002_indexes.cypher
├─ packages/
│  ├─ brains/                            # versioned, distributable brains
│  │  └─ math.calculus.power_rule/
│  │     ├─ cerebro.yml                  # manifest (name/version/license)
│  │     ├─ graph.jsonld                 # concepts/relations/sources
│  │     ├─ pedagogy.json                # thresholds/policies
│  │     ├─ spec/                        # unit tests for graph constraints
│  │     │  └─ paths.spec.json
│  │     └─ signatures/                  # (optional) pkg signatures
│  └─ tools/
│     ├─ validator/
│     │  ├─ package.json
│     │  ├─ src/index.ts                 # schema + lint (acyclic, sources, etc.)
│     │  └─ tsconfig.json
│     └─ cli/
│        ├─ package.json
│        └─ src/cli.ts                   # `cerebro pkg validate|pack|publish`
├─ sdk/
│  ├─ js/
│  │  ├─ package.json
│  │  ├─ src/client.ts                   # Query/Update/Trace client
│  │  └─ src/types.ts
│  └─ python/
│     ├─ pyproject.toml
│     └─ cerebro_sdk/client.py
├─ examples/
│  ├─ js-langchain/
│  │  ├─ package.json
│  │  └─ index.ts                        # Tool wrapper for LangChain
│  └─ python-openai/
│     └─ tool_calling.ipynb              # Function-calling usage
└─ tests/
   ├─ api/
   │  └─ test_query_update_trace.py      # pytest
   ├─ pedagogy/
   │  └─ test_mastery_update.py
   └─ e2e/
      └─ test_end_to_end.sh

```

***

### Key files (drop-in content)

#### `apps/api/main.py` (FastAPI – minimal endpoints)

```python
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import List, Optional
from graph import get_prereqs_below_threshold, get_trace, upsert_mastery_event
from pedagogy import update_mastery

app = FastAPI(title="CEREBRO API", version="v1")

class QueryReq(BaseModel):
    concept: str
    learner: Optional[str] = None
    depth: int = 2

class Prereq(BaseModel):
    id: str
    mastery: float | None = None
    minMastery: float | None = None

class QueryRes(BaseModel):
    concept: str
    summary: str
    rule: str | None = None
    prerequisites: List[Prereq]
    examples: List[str] | None = None
    sources: List[str] | None = None
    path: List[str]

class UpdateReq(BaseModel):
    learner: str
    concept: str
    correct: bool
    difficulty: float = Field(0.5, ge=0.0, le=1.0)

class TraceReq(BaseModel):
    concept: str
    learner: Optional[str] = None

@app.post("/v1/query", response_model=QueryRes)
def query(req: QueryReq):
    prereqs = get_prereqs_below_threshold(req.concept, req.learner, req.depth)
    path = [p["id"] for p in prereqs] + [req.concept]
    return QueryRes(
        concept=req.concept,
        summary=f"Structured explanation for {req.concept}",
        prerequisites=[Prereq(**p) for p in prereqs],
        sources=[],
        path=path
    )

@app.post("/v1/update")
def update(req: UpdateReq):
    # pull old mastery + last practiced (omitted: fetch from DB)
    old_p, hours = 0.4, 24.0
    new_p = update_mastery(old_p, req.correct, req.difficulty, hours)
    upsert_mastery_event(req.learner, req.concept, new_p, req.correct, req.difficulty)
    return {"ok": True, "mastery": new_p}

@app.post("/v1/trace")
def trace(req: TraceReq):
    return get_trace(req.concept)

```

`apps/api/pedagogy.py` (logistic update + decay)

```python
import math
def update_mastery(p: float, correct: bool, difficulty: float = 0.5, hours: float = 24.0) -> float:
    def sigmoid(x): return 1/(1+math.exp(-x))
    def logit(x):   return math.log(x/(1-x))
    alpha, beta = 1.2, 0.015
    w = 1 + (difficulty - 0.5)
    delta = (1 if correct else -1) * w - beta * hours
    return max(0.001, min(0.999, sigmoid(logit(p) + alpha*delta)))

```

`apps/api/graph.py` (Neo4j adapters – subset)

```python
from neo4j import GraphDatabase
import os
URI = os.getenv("NEO4J_URI", "bolt://localhost:7687")
USER = os.getenv("NEO4J_USER", "neo4j")
PASS = os.getenv("NEO4J_PASS", "devpass")
driver = GraphDatabase.driver(URI, auth=(USER, PASS))

def get_prereqs_below_threshold(target: str, learner: str|None, depth: int):
    with driver.session() as s:
        data = s.run("""
        MATCH (t:Concept {id:$target})<- [r:REQUIRES*1..$depth] - (p:Concept)
        OPTIONAL MATCH (u:Learner {id:$learner})-[m:MASTERY_OF]->(p)
        RETURN p.id AS id, coalesce(m.p, 0.0) AS mastery,
               coalesce(last(r).min_mastery, 0.7) AS minMastery
        """, target=target, learner=learner).data()
        return [dict(x) for x in data if x["mastery"] < x["minMastery"]]

def get_trace(target: str):
    with driver.session() as s:
        rec = s.run("""
        MATCH p=(a:Concept)-[:REQUIRES*0..]->(t:Concept {id:$target})
        WITH nodes(p) AS ns, relationships(p) AS rs
        RETURN [n IN ns | n.id] AS nodes,
               [ {from:startNode(r).id, to:endNode(r).id, type:type(r)} | r IN rs ] AS edges
        ORDER BY size(rs) ASC LIMIT 1
        """, target=target).single()
        return rec.data() if rec else {"nodes":[target], "edges":[]}

def upsert_mastery_event(learner: str, concept: str, p: float, correct: bool, diff: float):
    with driver.session() as s:
        s.run("""
        MERGE (u:Learner {id:$learner})
        MATCH (c:Concept {id:$concept})
        MERGE (u)-[m:MASTERY_OF]->(c)
        SET m.p = $p, m.samples = coalesce(m.samples,0)+1, m.last_practiced = timestamp()
        MERGE (u)-[:PRACTICED {ts: timestamp(), correct:$correct, difficulty:$diff}]->(c)
        """, learner=learner, concept=concept, p=p, correct=correct, diff=diff)

```

db/migrations/001\_init\_constraints.cypher

```cypher
CREATE CONSTRAINT concept_id IF NOT EXISTS FOR (c:Concept) REQUIRE c.id IS UNIQUE;
CREATE CONSTRAINT learner_id IF NOT EXISTS FOR (l:Learner) REQUIRE l.id IS UNIQUE;
```

`db/seed/math.calculus.power_rule.jsonld` (example brain)

```json
{
  "@context": "https://schema.cerebros.ai/v1",
  "@id": "pkg:math.calculus.power_rule",
  "@type": "CerebroPackage",
  "manifest": { "name": "Calculus (Power Rule)", "version": "1.0.0", "license": "Apache-2.0" },
  "graph": {
    "concepts": [
      {
        "@id": "concept:calc.power_rule",
        "@type": "Concept",
        "label": "Power Rule for Derivatives",
        "description": "d/dx [x^n] = n*x^(n-1)",
        "prerequisites": ["concept:algebra.exponents"],
        "sources": ["source:textbook.calculus.ch3"]
      },
      { "@id": "concept:algebra.exponents", "@type": "Concept", "label": "Exponents" }
    ],
    "relations": [
      { "from": "concept:algebra.exponents", "to": "concept:calc.power_rule",
        "type": "requires", "constraints": { "min_mastery": 0.8 } }
    ],
    "sources": [
      { "@id": "source:textbook.calculus.ch3", "title": "Calculus, Chapter 3" }
    ]
  },
  "pedagogy": { "thresholds": { "default_min_mastery": 0.7 } }
}

```

#### `sdk/js/src/client.ts` (tiny JS client)

```
export class CerebroClient {
  constructor(private cfg: { baseUrl?: string; apiKey?: string } = {}) {}
  private url(p: string) { return `${this.cfg.baseUrl ?? "http://localhost:8000"}${p}`; }

  async query(req: { concept: string; learner?: string; depth?: number }) {
    const r = await fetch(this.url("/v1/query"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(req) });
    return r.json();
  }
  async update(req: { learner: string; concept: string; correct: boolean; difficulty?: number }) {
    const r = await fetch(this.url("/v1/update"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(req) });
    return r.json();
  }
  async trace(req: { concept: string; learner?: string }) {
    const r = await fetch(this.url("/v1/trace"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(req) });
    return r.json();
  }
}

```

### `docker-compose.yml` (Neo4j + API)

```yaml
version: "3.9"
services:
  neo4j:
    image: neo4j:5
    environment:
      NEO4J_AUTH: neo4j/devpass
    ports: [ "7474:7474", "7687:7687" ]
    volumes:
      - neo4j_data:/data
  api:
    build: ./apps/api
    environment:
      NEO4J_URI: bolt://neo4j:7687
      NEO4J_USER: neo4j
      NEO4J_PASS: devpass
    ports: [ "8000:8000" ]
    depends_on: [ neo4j ]
volumes:
  neo4j_data:

```

apps/api/Dockerfile

```docker
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

```

apps/api/requirements.txt

```makefile
fastapi==0.111.0
uvicorn[standard]==0.30.0
neo4j==5.20.0
pydantic==2.8.2

```

### Make targets & local workflow

**Makefile**

```makefile
.PHONY: up seed api test

up:
\tdocker compose up -d --build

seed:
\tpython db/seed/load.py db/seed/math.calculus.power_rule.jsonld

api:
\tuvicorn apps.api.main:app --reload

test:
\tpytest -q

```

**db/seed/load.py** (simplified)

```python
import json, sys
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j","devpass"))

with open(sys.argv[1]) as f: pkg = json.load(f)
concepts = pkg["graph"]["concepts"]; relations = pkg["graph"]["relations"]; sources = pkg["graph"]["sources"]

with driver.session() as s:
    for src in sources:
        s.run("MERGE (s:Source {id:$id}) SET s.title=$title", id=src["@id"], title=src.get("title",""))
    for c in concepts:
        s.run("MERGE (c:Concept {id:$id}) SET c.label=$label, c.description=$desc",
              id=c["@id"], label=c.get("label",""), desc=c.get("description",""))
        for sid in c.get("sources", []):
            s.run("""MATCH (c:Concept {id:$cid}), (s:Source {id:$sid})
                     MERGE (c)-[:HAS_SOURCE]->(s)""", cid=c["@id"], sid=sid)
    for r in relations:
        s.run("""MATCH (a:Concept {id:$a}), (b:Concept {id:$b})
                 MERGE (a)-[rel:REQUIRES]->(b)
                 SET rel.min_mastery = $m""",
              a=r["from"], b=r["to"], m=r.get("constraints", {}).get("min_mastery", 0.7))
print("Loaded package:", pkg["manifest"]["name"])

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://cerebros.gitbook.io/cerebros/technical-architecture/starter-repo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
