Back to projects

AI / ML · Shipped 2026

Personal Learning Agent

A local-first agent that ingests AI/ML videos into a concept-and-tool knowledge base, answers with clickable timestamps, and tracks what I already know so it stops recommending it. Runs on CPU at $0.

Year
2026
Role
Solo build
Stack
Python, LangGraph, LangChain, Chroma, SQLite, Claude, Discord, Obsidian
The agent answering a question about how retrieval augmented generation reduces hallucination, with two of its claims marked as citations and the three source videos listed underneath with their YouTube timestamps, above the token count and zero cost for the query

What it does

I could not keep up with AI/ML content, and worse, I kept re-watching explanations of things I already understood because I had no record of what I had covered.

So pla ingests the videos from the channels I follow, tags them against a controlled vocabulary of 50 concepts and 45 tools, and answers my questions with citations that link to the exact second in the source video. Then it tracks what I have watched and stops recommending it.

You can drive it from a CLI, from a Discord bot by DMing it a question, or read the whole knowledge base as a linked note graph inside Obsidian.

The feature I actually use

pla plan <video> is the part that changed how I learn.

Given a video, it labels each timestamped segment by whether I already know that material, and tells me which spans to watch and which to skip. A sixty minute talk usually contains about twelve minutes I have not seen before, and this finds them. It logs partial watches, so --until 8:30 records where I stopped and picks up there next time.

That only works because coverage is modelled properly rather than as a watched flag. A rule-based knowledge-tracing layer moves every concept through new -> learning -> covered as I log what I watch, and covered concepts get deprioritised in answers as well as dropped from recommendations.

The measurement I am most glad I ran

I built the retrieval pipeline three times: once plain, once in LangChain, once in LangGraph. Not as an exercise, but because I wanted to know what the frameworks actually cost, and every discussion of it I could find was an opinion.

orchestration overhead, per query
  plain        2.12 ms
  LangChain    2.43 ms
  LangGraph    3.32 ms

end-to-end median, same questions
  plain        5.45 s     1497 output tokens
  LangGraph    5.97 s     1587 output tokens
  LangChain    6.24 s     1842 output tokens

The orchestration overhead is about one millisecond of a five and a half second query. It is noise. Anyone choosing between these on performance grounds is optimising something that does not matter, and I would not have believed that firmly enough to say it out loud without having measured it.

The difference that is real is the token count. LangChain’s prompting produced 23% more output tokens for the same questions, and on a metered API that is the actual bill. So the honest basis for choosing is legibility and token behaviour, not speed. I kept LangGraph, because an explicit state machine makes branching debuggable, and that is worth 0.5 seconds.

What retrieval scores

46 hand-labelled questions against a committed corpus, local bge-small embeddings on CPU, k=8:

recall@8      0.978
recall@3      0.924
recall@1      0.739
MRR           0.855
judge score   4.72 / 5

The number worth staring at is recall@1 at 0.739. Roughly one question in four does not put the right document first, which is fine for a system that reads eight of them before answering and would not be fine for one that reads one. Recall@8 being 0.978 is what makes the design work.

A regression guard runs these in CI and fails loudly if retrieval or answer quality drops, so the numbers cannot quietly rot.

It costs nothing to run

This was a constraint, not an accident. Embeddings are always local sentence-transformers on CPU, no key required. For generation the default provider shells out to Claude Code on an existing subscription rather than billing an API, and Ollama is a fully local alternative. There is also a fake provider that the tests run against, so the suite needs no network and no credentials.

That is also why the whole thing is local-first: the knowledge base is SQLite plus Chroma on my own disk, and the Obsidian export means the output survives the project.

Details that took more thought than expected

Canonicalization. “RAG” and “retrieval augmented generation” are the same concept, and if the tagger treats them as two nodes the graph quietly becomes useless. Every concept carries an alias list matched case-insensitively, and canonicalization runs before anything is written.

Batch safety. Ingest is idempotent and one failing video never kills the run. Polling a channel means processing things that will occasionally be malformed, private, or missing captions, and a pipeline that aborts on the first bad item is a pipeline you stop trusting to run unattended.

What is still open

The eval corpus is committed fixture documents rather than a sample of real ingested videos, so the retrieval numbers describe the retriever more than they describe the lived system. The judge is an LLM, unvalidated against human labels. And one of the 46 questions scores zero recall, which I have not yet diagnosed.

161 tests, CI green.