# CEREBRO Structure

## 0) Mental model (one sentence)

A **CEREBRO** = **temporal knowledge graph** (+ pedagogy + explainability) exposed by a tiny **Query/Update/Trace** API and backed by a graph store.

***

## 1) Data model (portable JSON-LD + runtime types)

#### 1.1 JSON-LD (portable “brain” package)

```json
{
  "@context": "https://schema.cerebros.ai/v1",
  "@id": "pkg:math.calculus.power_rule",
  "@type": "CerebroPackage",
  "manifest": {
    "name": "Calculus (Power Rule)",
    "version": "1.2.0",
    "license": "Apache-2.0",
    "authors": ["alberto@cerebros.ai"]
  },
  "graph": {
    "concepts": [
      {
        "@id": "concept:calc.power_rule",
        "@type": "Concept",
        "label": "Power Rule for Derivatives",
        "description": "d/dx [x^n] = n*x^(n-1)",
        "tags": ["calculus", "derivative"],
        "prerequisites": ["concept:algebra.exponents"],
        "sources": ["source:textbook.calculus.ch3"]
      },
      {
        "@id": "concept:algebra.exponents",
        "@type": "Concept",
        "label": "Exponents",
        "description": "Laws of 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, Ch. 3"}
    ]
  },
  "pedagogy": {
    "thresholds": {"default_min_mastery": 0.7}
  }
}
```

This mirrors the whitepaper’s node/edge schemas and packaging idea.

#### 1.2 Runtime Types (TypeScript)

```ts
// core graph
export type ConceptId = `concept:${string}`;
export type SourceId  = `source:${string}`;

export interface Concept {
  id: ConceptId;
  label: string;
  description?: string;
  tags?: string[];
  prerequisites?: ConceptId[];
  sources?: SourceId[];
  createdAt?: string;
  updatedAt?: string;
}

export interface Relation {
  from: ConceptId;
  to: ConceptId;
  type: "requires" | "is_a" | "derived_from" | "contradicts" | "example_of" | "part_of";
  constraints?: { min_mastery?: number };
  provenance?: SourceId[];
}

export interface Source {
  id: SourceId;
  title: string;
  url?: string;
}

export interface Mastery {
  learner: string;               // pseudonymous id
  concept: ConceptId;
  p: number;                     // [0..1]
  lastPracticed?: string;
  samples?: number;
  rollingAccuracy?: number;
}

export interface CerebroGraph {
  concepts: Record<ConceptId, Concept>;
  relations: Relation[];
  sources?: Record<SourceId, Source>;
}

// public API payloads
export interface QueryRequest { concept: ConceptId; learner?: string; depth?: number }
export interface QueryResponse {
  concept: ConceptId;
  summary: string;
  rule?: string;
  prerequisites: Array<{ id: ConceptId; mastery?: number; minMastery?: number }>;
  examples?: string[];
  sources?: SourceId[];
  path: ConceptId[];
}

export interface UpdateRequest {
  learner: string;
  concept: ConceptId;
  correct: boolean;
  difficulty?: number; // 0..1
  ts?: string;
}

export interface TraceRequest { concept: ConceptId; learner?: string }
export interface TraceResponse {
  nodes: ConceptId[];
  edges: Array<{ from: ConceptId; to: ConceptId; type: Relation["type"]; source?: SourceId[] }>;
}
```

These types follow the **Query/Update/Trace** triad.

***

## 2) API surface (FastAPI example)

```python
# app.py
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import List, Optional, Literal

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

ConceptId = str

class QueryRequest(BaseModel):
    concept: ConceptId
    learner: Optional[str] = None
    depth: int = 1

class Prereq(BaseModel):
    id: ConceptId
    mastery: Optional[float] = None
    minMastery: Optional[float] = None

class QueryResponse(BaseModel):
    concept: ConceptId
    summary: str
    rule: Optional[str] = None
    prerequisites: List[Prereq]
    examples: Optional[List[str]] = None
    sources: Optional[List[str]] = None
    path: List[ConceptId]

class UpdateRequest(BaseModel):
    learner: str
    concept: ConceptId
    correct: bool
    difficulty: float = Field(0.5, ge=0.0, le=1.0)
    ts: Optional[str] = None

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

@app.post("/v1/query", response_model=QueryResponse)
def query(req: QueryRequest): ...
@app.post("/v1/update")
def update(req: UpdateRequest): ...
@app.post("/v1/trace", response_model=dict)
def trace(req: TraceRequest): ...
```

The endpoints match the minimal public surface described in the whitepaper.

***

## 3) Storage (Neo4j property graph) + indices

**Nodes**

* `(:Concept {id, label, description, tags})`
* `(:Source {id, title, url})`
* `(:Learner {id})`

**Edges**

* `(:Concept)-[:REQUIRES {min_mastery}]->(:Concept)`
* `(:Learner)-[:MASTERY_OF {p, last_practiced, samples, rolling_acc}]->(:Concept)`
* `(:Concept)-[:HAS_SOURCE]->(:Source)`
* `(:Learner)-[:PRACTICED {correct, difficulty, ts}]->(:Concept)`

**Example Cypher**

```cypher
// fetch prereqs below threshold for a learner
MATCH (c:Concept {id:$target})<-[:REQUIRES*1..$depth]-(p:Concept)
OPTIONAL MATCH (u:Learner {id:$learner})-[m:MASTERY_OF]->(p)
WITH p, c, m, relationships( (p)-[:REQUIRES*1..]->(c) ) AS reqs
WHERE coalesce(m.p, 0.0) < coalesce(last(reqs).min_mastery, 0.7)
RETURN p.id AS id, coalesce(m.p, 0.0) AS mastery, coalesce(last(reqs).min_mastery, 0.7) AS minMastery
ORDER BY minMastery DESC;
```

Indices: `:Concept(id)`, `:Learner(id)`, composite on `(:Learner)-[:MASTERY_OF]->(:Concept)`.

***

## 4) Core algorithms (clean, small)

#### 4.1 Prerequisite closure + path planning

```ts
function prereqClosure(G: CerebroGraph, target: ConceptId, depth=1): Set<ConceptId> {
  const seen = new Set<ConceptId>();
  function dfs(node: ConceptId, d: number) {
    if (d < 0 || seen.has(node)) return;
    seen.add(node);
    const incoming = G.relations.filter(r => r.to === node && r.type === "requires");
    for (const r of incoming) dfs(r.from, d - 1);
  }
  dfs(target, depth);
  seen.delete(target);
  return seen;
}

function planPath(G: CerebroGraph, target: ConceptId, mastery: (c: ConceptId)=>number): ConceptId[] {
  // Topological order of requires-subgraph, pruned by mastery thresholds
  const need = Array.from(prereqClosure(G, target, Infinity)).filter(c => {
    const reqEdge = G.relations.find(r => r.from === c && r.to === target && r.type==="requires");
    const min = reqEdge?.constraints?.min_mastery ?? 0.7;
    return mastery(c) < min;
  });

  // simple topo-sort
  const deps = (n: ConceptId) => G.relations.filter(r => r.type==="requires" && r.to===n).map(r => r.from);
  const order: ConceptId[] = [];
  const temp = new Set<ConceptId>(), perm = new Set<ConceptId>();
  function visit(n: ConceptId) {
    if (perm.has(n)) return;
    if (temp.has(n)) throw new Error("Cycle in requires graph");
    temp.add(n);
    for (const d of deps(n)) if (need.includes(d)) visit(d);
    temp.delete(n); perm.add(n); order.push(n);
  }
  for (const n of need) visit(n);
  order.push(target);
  return order;
}
```

Matches the paper’s **topo-sort + threshold pruning** approach.

#### 4.2 Mastery update (logistic w/ decay)

```ts
function updateMastery(p: number, correct: boolean, difficulty=0.5, hoursSinceLast=24) {
  const sigmoid = (x:number)=>1/(1+Math.exp(-x));
  const logit   = (x:number)=>Math.log(x/(1-x));

  const alpha = 1.2;            // learning rate
  const beta  = 0.015;          // decay rate per hour
  const w     = (d:number)=> 1 + (d - 0.5); // harder → bigger step

  const delta = (correct ? +1 : -1) * w(difficulty) - beta*hoursSinceLast;
  return Math.min(0.999, Math.max(0.001, sigmoid(logit(p) + alpha*delta)));
}
```

Same spirit as the whitepaper’s logistic update with decay and difficulty weighting.

***

## 5) Minimal Query/Trace/Update implementation (FastAPI + Neo4j)

```python
from neo4j import GraphDatabase
from datetime import datetime

driver = GraphDatabase.driver(URI, auth=(USER, PASS))

def get_mastery(tx, learner, concept):
    rec = tx.run("""
        MATCH (u:Learner {id:$learner})-[m:MASTERY_OF]->(c:Concept {id:$concept})
        RETURN m.p AS p, m.last_practiced AS lp
    """, learner=learner, concept=concept).single()
    if not rec: return 0.0, None
    return rec["p"] or 0.0, rec["lp"]

@app.post("/v1/query", response_model=QueryResponse)
def query(req: QueryRequest):
    with driver.session() as s:
        prereqs = 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=req.concept, depth=req.depth, learner=req.learner).data()

        # toy summary/rule; in prod, store canonical rule on concept node
        summary = "Structured explanation for " + req.concept
        rule    = None

        path = [x["id"] for x in prereqs if x["mastery"] < x["minMastery"]] + [req.concept]
        return {
          "concept": req.concept,
          "summary": summary,
          "rule": rule,
          "prerequisites": prereqs,
          "examples": [],
          "sources": [],
          "path": path
        }

@app.post("/v1/update")
def update(req: UpdateRequest):
    now = req.ts or datetime.utcnow().isoformat()
    with driver.session() as s:
        def work(tx):
            p, lp = get_mastery(tx, req.learner, req.concept)
            # very rough decay (hours)
            hours = 24.0
            new_p = float(updateMastery_py(p, req.correct, req.difficulty, hours))
            tx.run("""
                MERGE (u:Learner {id:$learner})
                MATCH (c:Concept {id:$concept})
                MERGE (u)-[m:MASTERY_OF]->(c)
                SET m.p = $p, m.last_practiced = $ts,
                    m.samples = coalesce(m.samples,0)+1
                MERGE (u)-[:PRACTICED {ts:$ts, correct:$correct, difficulty:$diff}]->(c)
            """, learner=req.learner, concept=req.concept, p=new_p, ts=now,
                 correct=req.correct, diff=req.difficulty)
        s.execute_write(work)
    return {"ok": True}

@app.post("/v1/trace")
def trace(req: TraceRequest):
    with driver.session() as s:
        recs = s.run("""
          MATCH path = (p:Concept)-[:REQUIRES*0..]->(t:Concept {id:$target})
          WITH nodes(path) AS ns, relationships(path) 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=req.concept).single()
        return recs.data() if recs else {"nodes":[req.concept], "edges":[]}
```

This is intentionally tiny but complete: **works with Neo4j**, returns **prereqs + path**, logs **practice events**, and exposes a **trace**.

***

## 6) Function-calling / MCP schemas (for LLM planners)

```json
{
  "name": "cerebro.query",
  "description": "Retrieve structured explanation for a concept with prereqs and sources",
  "parameters": {
    "type": "object",
    "properties": {
      "concept": { "type": "string" },
      "learner": { "type": "string" },
      "depth":   { "type": "integer", "minimum": 1, "default": 2 }
    },
    "required": ["concept"]
  }
}
```

Provide similar schemas for `cerebro.update` and `cerebro.trace`. This enables **deterministic tool use** from the LLM layer.

***

## 7) Minimal repo layout

```
cerebro/
├─ packages/
│  └─ math.calculus.power_rule.jsonld      # distributable brain
├─ server/
│  ├─ app.py                               # FastAPI endpoints
│  ├─ graph.py                             # Neo4j adapters
│  └─ pedagogy.py                          # mastery/decay functions
├─ sdk/
│  └─ ts/ (client with Query/Update/Trace)
├─ tests/
│  ├─ test_paths.py                        # “power_rule requires exponents”
│  └─ test_mastery.py
└─ tools/
   └─ validator.ts                         # schema + graph lint
```

This mirrors the **package/validator/graph/SDK** split suggested in the whitepaper.

***

## 8) End-to-end example (TypeScript client)

```ts
import { CerebroClient } from "@cerebros/sdk";

const client = new CerebroClient({ apiKey: process.env.CEREBRO_API_KEY! });

const q = await client.query({ concept: "concept:calc.power_rule", learner: "u123" });
/*
{
  concept: 'concept:calc.power_rule',
  summary: 'Structured explanation for concept:calc.power_rule',
  prerequisites: [
    { id: 'concept:algebra.exponents', mastery: 0.2, minMastery: 0.8 }
  ],
  path: ['concept:algebra.exponents', 'concept:calc.power_rule']
}
*/

await client.update({ learner: "u123", concept: "concept:algebra.exponents", correct: true, difficulty: 0.6 });

const tr = await client.trace({ concept: "concept:calc.power_rule" });
// => nodes and edges suitable for a graph viewer
```


---

# 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/cerebro-structure.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.
