From static PDFs to a living knowledgebase: How the kb CLI tool enables agents to access technical literature – including discovery, Vision AI, and structured JSON output.
1. The Problem: Expertise is Trapped in Silos
In the world of AI agents, accessing up-to-date and deep expertise is often the biggest hurdle. While LLMs have impressive general knowledge, they reach their limits with specific, mathematical, or highly topical subjects.
The most valuable knowledge is often found in technical books (PDF and EPUB), which are difficult for agents to access: 1. Unstructured Data: PDFs and EPUBs are optimized for humans, not machines. 2. Context Window: You can't simply copy 40 technical books into an agent's prompt. 3. Source Fidelity: Agents tend to hallucinate when they lack solid references. 4. Visual Content: Diagrams and illustrations in books often contain essential information that pure text extraction misses.
"The only way to make sense out of change is to plunge into it, move with it, and join the dance." — Alan Watts
For an AI agent, "joining the dance" means actively accessing an external knowledge base to provide valid answers.
2. The Solution: The kb CLI Tool
kb is a specialized Command Line Interface that bridges the gap between static technical literature and dynamic AI workflows. It implements a complete RAG pipeline (Retrieval-Augmented Generation) directly in the terminal.
Key Features
- Semantic Search: Finds relevant text passages based on meaning, not just keywords.
- RAG Answers: Answers complex questions by directly incorporating the sources found.
- Multi-KB Management: Separation of knowledge areas (e.g.,
fpfor Functional Programming,catfor Category Theory). - KB Discovery (
kb kbs): Agents can list all available knowledgebases with book titles – via--jsonas structured data. - Incremental Updates (
kb add): Add individual books to an existing KB without rebuilding the entire index. - PDF & EPUB Support: Automatic format detection for both book formats.
- Vision AI: Extraction and description of illustrations from books via GPT-4o Vision – image descriptions are indexed and included in RAG answers.
- Deep Links: Enables opening PDFs directly at the exact cited location (macOS).
- JSON-First: All outputs are available as JSON – ideal for integration into agents.
→ GitHub: github.com/mhennemeyer/knowledgebase
3. In Action: The Agent Workflow
The typical agent workflow with kb consists of two steps: Discovery and Query.
Step 1: Discovery – Which Knowledgebases Exist?
kb kbs --json
Output (JSON):
{
"knowledgebases": [
{
"name": "fp",
"books": 8,
"chunks": 4163,
"book_titles": ["Functional Programming in Scala", "Haskell Programming from First Principles", "..."]
},
{
"name": "cat",
"books": 8,
"chunks": 3090,
"book_titles": ["Category Theory for Programmers", "Category Theory Illustrated", "..."]
},
{
"name": "ai",
"books": 25,
"chunks": 14457,
"book_titles": ["Designing Autonomous AI", "..."]
}
]
}
The agent now knows the available knowledge areas, their depth (chunks), and the specific book titles. Without --name, kb ask queries only the default KB – with kb kbs, the agent makes an informed decision about which KB is relevant.
Step 2: Targeted Query
kb ask "What is a functor?" --name cat --json
Output (JSON):
{
"answer": "A functor is a concept from category theory that represents a mapping between categories. It consists of two mappings: one for objects and one for morphisms. These must preserve the structure (identity and composition)...",
"sources": [
{
"book": "Category Theory Illustrated",
"page": 202,
"score": 0.55,
"open_cmd": "open -a Skim 'path/to/book.pdf' --args -page 202"
},
{
"book": "Category Theory for Programmers",
"page": 122,
"score": 0.50
}
]
}
With the --json flag, the agent receives a structured answer that it can process immediately. It not only knows the answer but also the exact sources and could even offer to open the book for the user at the correct location.
4. Statistical Overview
The kb tool currently manages four thematic knowledge bases for this project, spanning 43 books in total:
| KB Name | Topic | Books | Chunks |
|---|---|---|---|
fp |
Functional Programming | 8 | 4,163 |
cat |
Category Theory | 8 | 3,090 |
arch |
Software Architecture | 1 | 898 |
ai |
AI & Agentic Systems | 25 | 14,457 |
These amounts of data would be unmanageable for an agent without semantic indexing.
5. Architecture: Pipeline with Vision AI
The technical pipeline of kb processes books in multiple stages:
PDF/EPUB → extract → Markdown + Images → chunk → Chunks → index → FAISS
↓ ↓
Vision AI (GPT-4o) Question → search → Chunks → LLM → Answer + Sources
describes images
Through the optional Vision AI stage (GPT-4o Vision), illustrations from books are extracted, described, and indexed as text chunks. Diagrams, graphics, and formulas become searchable knowledge available to the agent.
6. Integration: Why CLI?
The decision to implement the knowledgebase as a CLI tool follows the principle of Modular Intelligence. Instead of re-implementing RAG logic in every application, kb provides a universal interface:
- Independence: The tool works independently of the web platform or the specific agent.
- Composability: Via pipes (
|), search results can be directed into other tools (likegreporjq). - Agent-Ready: Modern agents are excellent at operating CLI tools. They "understand" help pages from Typer/Click and can set parameters correctly.
- Discovery-First: The
kb kbs --jsoncommand enables agents to explore available knowledge areas before making a query – a self-service approach for autonomous agents.
Try It Yourself
This is exactly what makes kb so attractive for agents: the kbs --json response is data that can be processed by pure functions. Here is a small discovery match: given the JSON structure and a user question, the best-matching KB is selected via a simple keyword score – no LLM, no embeddings, just the standard library.
import json
import re
kbs_response = """
{
"knowledgebases": [
{"name": "fp", "books": 8, "chunks": 4163,
"book_titles": ["Functional Programming in Scala", "Haskell Programming from First Principles"]},
{"name": "cat", "books": 8, "chunks": 3090,
"book_titles": ["Category Theory for Programmers", "Category Theory Illustrated"]},
{"name": "ai", "books": 25, "chunks": 14457,
"book_titles": ["Designing Autonomous AI", "Building LLM Agents"]}
]
}
"""
def tokens(text):
return set(re.findall(r"[a-z]+", text.lower()))
def score_kb(kb, question_tokens):
"""Pure score: number of token hits across name + book titles."""
haystack = kb["name"] + " " + " ".join(kb["book_titles"])
return len(question_tokens & tokens(haystack))
def best_kb(kbs_json, question):
data = json.loads(kbs_json)
q = tokens(question)
ranked = sorted(
data["knowledgebases"],
key=lambda kb: (score_kb(kb, q), kb["chunks"]),
reverse=True,
)
return ranked[0]
questions = [
"What is a functor in Category Theory?",
"How do I build an autonomous LLM agent?",
"Explain monads in Haskell",
]
for q in questions:
kb = best_kb(kbs_response, q)
print(f"{q!r:50} → best KB: {kb['name']:4} (chunks={kb['chunks']})")
Extension idea: Replace the keyword score with a pure function score_by_overlap(kb, query_tokens) that additionally weighs the book titles higher than the KB name – the selection logic remains testable, with no external dependencies.
That is exactly the second, deeper step: book titles are more telling than the short KB name – so we weigh them higher. The selection logic stays a pure function, and each KB's score is transparent.
import json, re
kbs_response = """
{
"knowledgebases": [
{"name": "fp", "books": 8, "chunks": 4163,
"book_titles": ["Functional Programming in Scala", "Haskell Programming from First Principles"]},
{"name": "cat", "books": 8, "chunks": 3090,
"book_titles": ["Category Theory for Programmers", "Category Theory Illustrated"]},
{"name": "ai", "books": 25, "chunks": 14457,
"book_titles": ["Designing Autonomous AI", "Building LLM Agents"]}
]
}
"""
def tokens(text):
return set(re.findall(r"[a-zäöüß]+", text.lower()))
def score_by_overlap(kb, q, w_name: int = 1, w_title: int = 3) -> int:
"""Pure weighted score: titles count more than the KB name."""
name_hits = len(q & tokens(kb["name"]))
title_hits = len(q & tokens(" ".join(kb["book_titles"])))
return w_name * name_hits + w_title * title_hits
def rank(kbs_json, question):
data = json.loads(kbs_json)
q = tokens(question)
scored = [(kb["name"], score_by_overlap(kb, q)) for kb in data["knowledgebases"]]
return sorted(scored, key=lambda t: t[1], reverse=True)
for q in [
"What is a functor in category theory?",
"How do I build an autonomous LLM agent?",
"Explain monads in Haskell",
]:
ranking = rank(kbs_response, q)
print(f"{q!r:50} → ranking={ranking}")
Observation: the order follows the expressiveness of book titles, not the KB name – cat beats fp on a "category theory" question because both of its titles match.
7. Conclusion
kb transforms a collection of PDFs and EPUBs into a searchable brain – including visual content. The discovery workflow (kbs → ask --name) and consistent JSON support make the tool a natural interface for AI agents that need access to well-founded technical expertise.
Sources & Resources
- Knowledgebase Repo: github.com/mhennemeyer/knowledgebase
- FAISS: facebookresearch/faiss – Efficient similarity search.
- OpenAI Embeddings:
text-embedding-3-large. - OpenAI Vision:
GPT-4o Vision– Image analysis for indexing visual book content.