boost / docs / langchain integration
Interop · shipped in PR #466The skill catalog, behind LangChain's retriever contract.
boost's tapped catalog is 10,000+ retrievable procedures with frontmatter,
provenance and a lock file. boost-langchain makes it addressable from a Python
agent runtime: a retriever over the same measured engine boost search
runs on, a document loader for one SKILL.md, and a LangGraph node that pulls
the right procedure into a run mid-flight. No API key, ever — and nothing tapped means
[], not an exception.
Two audiences — one of them should leave now
If you are a LangChain developer who just wants
skills in your editor — Claude Code, Cursor, Windsurf, Gemini CLI — you do not
need this package. Install the boost CLI
and run boost install <skill>: no langchain dependency at all.
This package exists for the other direction: putting the catalog inside a LangChain application, so a chain can search it, stuff it into context, or route on it at runtime. That scoping decision shaped the whole API — it is a retriever, not an installer. Installing is the CLI's job, with its lock file and agent symlinks; duplicating that path here would have created a second, redundant way to install and nothing else. What a Python agent runtime actually lacks is reach: the same procedure otherwise gets re-written as a prompt string by hand.
Everything that decides what comes back — ranking, fusion, dedupe, the degrade
ladder — stays in boost_cli.core, where the repo's
eval gate measures it. This package only translates the result
into LangChain's types.
Three thin seams over one measured engine
Queries flow left to right; Documents flow back. The seams own translation; the engine owns retrieval; the gate below the engine is why wrapping it beats re-embedding the catalog.
The seams never rank. Everything that decides what comes back lives in boost_cli.core, under the gate.
- Reuse, don't re-embed. The retriever wraps
rag.retrieve_any— the engineboost searchruns on — rather than re-embedding the catalog into a vector store. Retrieval quality is the reason: boost's Tier-1 gate floors recall@k, hit@1, MRR and nDCG@k over a golden query set on every merge, so this retriever ships with numbers rather than claims. A fresh embedding pipeline here would start from zero evidence. - Degrade to
[], never raise. The always-on engine is a pure-stdlib BM25 full-content index that builds itself on first use (rag.ensure()makes a fresh tap behave likeboost searchdoes). With boost's[rag]extra the dense engine joins by reciprocal rank fusion; a Voyage or OpenAI key upgrades embedding quality but a local model ships with the extra. Nothing tapped returns[]— a chain keeps running without boost context rather than crashing on it. One honest caveat: the BM25 tier tokenizes[a-z0-9], so non-Latin queries (CJK, emoji) retrieve nothing from it — the dense tier is what serves them. - One catalog, three kinds, one filter. boost indexes
skill(SKILL.md),rule(.mdc, .cursorrules, …) andworkflow(slash commands / subagents).kindnarrows retrieval to one of them — a router choosing among rules should not wade through skills — andNonesearches all three.
Twenty lines, standard chain shape
BoostRetriever is a
langchain-core BaseRetriever, so it is a Runnable and drops into
the usual retrieval-chain shape unchanged.
from boost_langchain import BoostRetriever retriever = BoostRetriever(k=4) docs = retriever.invoke("set up code review for a python repo") for d in docs: print(d.metadata["name"], d.metadata["kind"], d.metadata["tap"]) # It is a standard Runnable retriever, so the usual chain shape works: from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough chain = ( {"context": retriever, "question": RunnablePassthrough()} | prompt # your ChatPromptTemplate | model # your chat model | StrOutputParser() ) chain.invoke("how should this repo handle commit messages?") # kind narrows retrieval to one of boost's three item kinds: BoostRetriever(kind="rule") # only rules (.mdc, .cursorrules, ...) BoostRetriever(kind="workflow") # only slash commands / subagents
k
and kind are pydantic-checked fields: a typo'd kind would otherwise return
[] for every query — indistinguishable from an empty catalog — and a
negative k would silently drop the last hit via slice semantics. Both fail
loudly the moment you build the retriever instead.Each Document's page_content is the
item's indexed text — its name and one-line description followed by the full
body, i.e. exactly the surface BM25 scored, so what you retrieve is what matched. Pass
full_content=False for just the description (what a router choosing among
items wants, rather than a chain stuffing context). Metadata carries:
| key | what it holds |
|---|---|
name / kind | the item's name and which of the three kinds it is |
tap / source / version | provenance: registry, in-repo path, frontmatter version |
engine | which engine actually answered — "BM25 full-content", "dense vectors", or "hybrid RRF (BM25 + dense)" |
For one specific skill — no retrieval, just
loading — SkillMarkdownLoader yields a SKILL.md as a single Document: body
as content, frontmatter as metadata. One Document per skill, not one per chunk: a skill
file is already the author's chosen unit of instruction (run any LangChain splitter over
it if you want chunks). Parsing reuses boost's own frontmatter parser, so the file reads
exactly the way boost's catalog reads it.
from boost_langchain import SkillMarkdownLoader # From a path (a SKILL.md, or a directory containing one): doc = SkillMarkdownLoader("path/to/skill").load()[0] # From boost's canonical store, for a skill you already installed: doc = SkillMarkdownLoader.from_installed("brainstorming").load()[0] doc.page_content # the markdown body, frontmatter stripped doc.metadata # frontmatter keys (name, version, tags, ...) + source
Two details that earn their keep: the real file path always wins over any
frontmatter key that happens to be named source — provenance must state
where the bytes came from, and a self-reported value is the one thing that cannot. And
from_installed declines a name installed as a rule or workflow by
kind rather than with a bare missing-file error: those kinds materialize into agent
context files and keep no store copy to load — retrieve them with
BoostRetriever(kind="rule") instead.
Pull the procedure mid-run, not up front
The alternative this exists to avoid is stuffing
every procedure into the system prompt: that spends context on procedures the run never
needs and stops scaling the moment the catalog does. skill_context_node
retrieves against the current state instead, so each turn carries only the procedures that
turn asked for.
The no-op path is a contract, not an accident: a graph wired through this node must keep working on a machine with an empty catalog.
from boost_langchain import skill_context_node from langgraph.graph import StateGraph, MessagesState, START builder = StateGraph(MessagesState) builder.add_node("skills", skill_context_node(k=3)) # or kind="workflow", or a configured BoostRetriever builder.add_node("agent", call_model) # your model node builder.add_edge(START, "skills") builder.add_edge("skills", "agent")
- It retrieves on the last human message. Earlier human turns are already answered context; retrieving on them would re-inject procedures for questions the graph has moved past.
- Provenance rides along. Injected text steers the model, so when a run goes wrong the first question is which skill said that and where it came from. Every injected procedure is prefixed with its name, tap, in-repo path and version — a poisoned or stale skill is traceable from the transcript alone, without re-running retrieval.
kdefaults to 3 here, not the retriever's 8. These are full procedure bodies landing in a live conversation: three is a context budget, eight is a search page. Pass a configuredBoostRetrieverto control retrieval wholesale.- The node imports only
langchain_core. A graph node is just a callable from state to a partial state update, so langgraph itself stays where the pyproject puts it — an optional extra.
Tracing you don't write, ground truth you already have
Because BoostRetriever is a
langchain-core BaseRetriever, LangSmith instrumentation traces
its calls automatically — enable tracing the standard way
(LANGSMITH_TRACING=true + a key) and every retrieval shows up with its query,
its documents, and the engine metadata naming which of boost's engines
answered.
Offline, boost's required gate already floors recall@k / hit@1 / MRR / nDCG@k over a
golden query set on every merge (the Evaluation page is that
story). To run online evals against the same ground truth, publish the
golden set as a LangSmith dataset with the
publish_golden_dataset.py script (under
integrations/langchain/scripts/) — key-gated,
opt-in, run from a boost checkout. Re-running replaces examples wholesale, so the
dataset always mirrors tests/eval/golden.jsonl rather than accreting. The
point is one set of judgments in both places: an online regression and an offline
regression mean the same thing.
None of this touches the required gate, which stays offline, deterministic and key-free — a required check that depends on a SaaS account is a required check that fails when someone else's billing lapses.
The honest dependency paragraph. boost's own [eval] extra pins the
langchain 0.3 stack, because ragas — through 0.4.3 — still imports a
ChatVertexAI path that langchain-community 0.4 removed, so
import ragas crashes beside langchain 1.x. What dissolves the conflict is
distribution isolation, not an unpin: boost-langchain is a separately
versioned package, so the langchain 1.x stack lives in its own environment and its own
CI leg while [eval] keeps its 0.3-stack pins in its own. The two majors
never co-install — pip refuses the combination loudly — and each surface tests against
the stack it really runs on. The unpin becomes a small follow-up the day ragas ships a
release after 0.4.3, which already carries the fix on its main branch.
One pip install, two optional extras
# the package — pulls in boost-skill-cli + langchain-core $ pip install boost-langchain # give boost something to search (once) $ boost tap anthropics/skills # optional: the LangGraph runtime for skill_context_node graphs $ pip install 'boost-langchain[langgraph]' # optional: publish the golden set as a LangSmith dataset $ pip install 'boost-langchain[langsmith]' $ python integrations/langchain/scripts/publish_golden_dataset.py published 91 examples to LangSmith dataset 'boost-golden-retrieval'
The dataset publish is illustrative output; it exits with a one-line hint rather than a traceback when the key or package is missing.
More depth: the package README covers the same ground as a reference, and the roadmap card holds the full reasoning — including the packaging decision and the corrected ragas story.