Procedural memory for tool-using AI agents. Capture the tool procedure that solved a task, replay it on exact repeats with zero planning calls, and guide similar tasks with a learned playbook.
v1.0 Β·
pip install learnkit-aiΒ· wrap one function Β· watch it stop re-planning.
LearnKit is an agent-agnostic SDK that makes tool-using AI agents learn from experience. It captures the tool-call procedure an agent uses to solve a task, then:
- replays it on exact repeats with zero planning/LLM calls, and
- guides sibling tasks with a distilled playbook so the model follows the proven shape instead of re-exploring.
The expensive, compressible part of a tool-using agent isn't the answer text β it's the planning loop (the back-and-forth LLM calls to decide which tools to call in what order). That plan is normally thrown away after every task. LearnKit keeps it, quality-gates it on the real tool outcome (not a fragile LLM judge), and reuses it.
Reproducible result (agentic matrix, 3/3 models PASS, equal task success):
+2.25 best quality lift Β· β38.4% pooled LLM planning calls on Qwen2.5-14B/32B and Llama-3.3-70B.
Memory is typed, quality-gated, attributed (help/harm/reuse per record), and lifecycle-managed (confidence decay, quarantine β promote, TTL) β auditable, deletable JSON, no model retraining.
LearnKit adds procedural memory (how to do a task) β distinct from the semantic/episodic memory that Mem0, Zep, and vector stores provide (facts and past conversations).
- Not plan caching. Plan caching keys a whole plan and trades accuracy for cost. LearnKit does workflow induction: hard-replay only on an exact match (zero LLM), and guide sibling tasks (the model still plans) β never a fuzzy replay that could silently produce the wrong result. Success holds; it doesn't degrade.
- Not hand-written Skills. LangChain / Deep Agents Skills are procedural memory
too, but you author and maintain each one by hand. LearnKit auto-induces
procedures from real successful runs, quality-gates them on the tool outcome,
tracks help/harm, and decays the stale ones β and it's framework-agnostic. It
even exports a Deep Agents-compatible library:
learnkit skills export --format deepagents.
flowchart LR
T[Task] --> W["@memory.agent_learn"]
W --> R["classify β retrieve β compose"]
R --> M{"procedure match?"}
M -->|exact| RP["replay_plan<br/>0 planning calls"]
M -->|sibling| G["guided ReAct<br/>follows playbook"]
M -->|none| C["cold ReAct<br/>explore + capture"]
RP --> O["tool-success gate"]
G --> O
C --> O
O -->|pass| D["distill procedure β SkillRecord"]
O -->|fail| X["demote"]
D --> S[("SQLite: records + runs")]
X --> S
S -->|next encounter| R
S --> DASH["FastAPI β React dashboard<br/>calls-reduced Β· replays Β· traces"]
The wrapped agent function never changes β the @memory.agent_learn decorator
orchestrates everything around it. Full, detailed diagrams live in
architecture/; see How it works
for the step-by-step.
π¨ A colored, presentation-ready version is in
architecture/high_level_architecture.mmdβ render it at mermaid.live and export a PNG.
To install from PyPI (recommended):
pip install learnkit-ai
# Or with integration extras:
pip install "learnkit-ai[langchain]"To install from local repo root:
pip install -e . # core SDK
pip install -e ".[langchain]" # adds LangChain + langchain-anthropic
pip install -e ".[dev]" # pytest + pytest-asyncioOther optional extras: mem0, zep, qdrant.
Set your Anthropic key (optional β only the LLM classifier/distiller use it; the agent path and benchmarks run keyless). PowerShell, persists across sessions:
[Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-...", "User")On bash/zsh: export ANTHROPIC_API_KEY=sk-ant-... in your shell rc.
python examples/agent_learn_demo.pyA runnable, offline (no API key) demo of the coldβwarm capture-and-replay loop: on first exposure the agent explores and LearnKit captures the productive tool procedure; on repeats it replays that procedure with zero planning calls.
import learnkit as lk
memory = lk.LearnKit(memory_backend="sqlite", scope="team")
@memory.agent_learn(domain="pipeline")
def my_tool_agent(task: str, _learnkit_context: str = "", _learnkit_tools=None) -> str:
# Record every tool call so LearnKit can learn / replay the productive procedure.
rows = _learnkit_tools.record("query", {"table": "users"}, run_query("users"))
_learnkit_tools.record("format", {"fmt": "csv"}, to_csv(rows))
return "report ready"
# Same task, called twice β run 2 replays run 1's captured procedure (zero planning calls).
my_tool_agent("Build an active-user CSV report")
my_tool_agent("Build an active-user CSV report")Valid scope values: "user", "team", "public" (see learnkit/schemas/base.py).
Don't want to hand-check has_plan/plan_steps? Use the built-in ReAct runner.
It prepares the run, retrieves any matching procedure, auto-replays exact
matches with zero planning calls, and otherwise drives your planner while
capturing the trajectory:
from learnkit import run_react_agent, LLMStep, ToolCall
def planner(task, context, history):
# One planning turn. Return tool calls to make, and/or a final answer.
# `context` already contains playbook guidance for sibling tasks.
...
return LLMStep(tool_calls=[ToolCall("query", {"table": "users"})], final=None)
result = run_react_agent(memory, task, tools, planner, exploration_tools={"list_tables"})
print(result.replayed, result.llm_calls, result.tool_calls) # True 0 3 on an exact repeatThis path supports exact replay (zero-LLM for exact re-encounters) and guided
sibling reuse. A runnable, offline demo (no API key) lives at
examples/agent_learn_demo.py. See
benchmarks/injection_ablation.py for a quality-focused ablation that isolates
the effect of playbook injection on novel sibling tasks.
LangChain, LangGraph, AutoGen, CrewAI, LlamaIndex, and the OpenAI Agents SDK are all supported through the universal adapter contract described in Framework integrations below. Each adapter captures tool calls onto the run so the agent path learns and replays procedures with no change to your agent logic.
LearnKit is framework-agnostic. Every integration subclasses one universal
contract (learnkit.adapters.BaseAdapter), so they all expose the same
agent-path API β start_run β inject memory + arm the ToolTracker,
complete_run β capture and distill the procedure β and an exception-safe
session() / asession() lifecycle.
| Framework | Adapter | Install | Native hook |
|---|---|---|---|
| LangChain | LangChainAdapter |
learnkit-ai[langchain] |
LearnKitCallbackHandler (BaseCallbackHandler) |
| LangGraph | LangGraphAdapter |
learnkit-ai[langgraph] |
as_node() graph node |
| AutoGen / AG2 | AutoGenAdapter |
learnkit-ai[autogen] |
inject() system-message + ChatResult capture |
| CrewAI | CrewAIAdapter |
learnkit-ai[crewai] |
step_callback() (AgentAction) |
| LlamaIndex | LlamaIndexAdapter |
learnkit-ai[llamaindex] |
LearnKitLlamaHandler (FUNCTION_CALL events) |
| OpenAI Agents SDK | OpenAIAgentsAdapter |
learnkit-ai[openai-agents] |
LearnKitRunHooks (RunHooks) |
| Raw OpenAI / Anthropic | OpenAIRawAdapter |
core | wraps the chat call |
from learnkit import LearnKit
from learnkit.adapters import get_adapter
lk = LearnKit(memory_backend="sqlite")
adapter = get_adapter("crewai")(lk) # resolve any adapter by name
with adapter.session(task) as run: # exception-safe lifecycle
tools = adapter.wrap_tools(run, tools) # agent path: capture tool calls
run.response = my_agent(run.context, tools)Any third-party package can register its own adapter without a PR β declare a
learnkit.adapters entry point and LearnKit discovers it lazily via
get_adapter(name) / available_adapters().
When no LLM provider key is set, LearnKit runs keyless: task classification
falls back to a deterministic heuristic instead of a network call, so the agent
path and the deterministic benchmarks run with zero credentials and zero
latency. Force it explicitly with LEARNKIT_OFFLINE=1; it is auto-detected
otherwise (no provider key and no LEARNKIT_CLASSIFIER_MODEL override).
The agent function never changes. The decorator orchestrates everything around it.
- User calls your wrapped agent with a task.
- Classify β
TaskClassifierreturns a domain vector, e.g.{"Python": 0.9, "Concurrency": 0.7}. - Retrieve β
SemanticRetrieverpulls relevant records (FTS5 lexical + optional dense rerank), filtered bydomainandscope. - Compose β
compose_contextformats records into a bounded prompt block (β€ 8 records, β€ 1,200 tokens, inference mode =PRESCRIPTIVE/GUIDED/EXPLORATORYbased on top-record confidence). - Run β your function executes with
_learnkit_contextinjected as a kwarg. - Evaluate β on the agent path the outcome is gated on the real tool success (
ToolTracker.outcome_score()), not a fragile LLM judge; an optional judge scores 0β5 when no tool signal is available. - Distill β a passing run captures the cleaned productive tool sequence into a procedural
SkillRecord(procedure/tool_sequence/trigger) for replay, plus optional facts/failures. Below threshold, aFailureRecordis stored directly so future runs avoid the same path. - Persist β records are written via the active backend; the trajectory is registered against a per-run ID for inspection.
LearnKit stores seven typed record kinds (learnkit/schemas/):
| Record | Activates as | Notes |
|---|---|---|
SkillRecord |
quarantine |
Promoted to active after the configured probation window |
FactRecord |
quarantine |
Same probation as skills |
FailureRecord |
active immediately |
Per ReaComp β agents must avoid known dead ends as fast as possible |
StrategyRecord |
quarantine |
Higher-level approaches |
PreferenceRecord |
quarantine |
User / team preferences |
TraceRecord |
active |
Raw execution trace for replay |
HeuristicRecord |
quarantine |
Domain heuristics |
Bounded memory is enforced at retrieval: the router caps results at 8 records / ~1,200 tokens before the composer formats them.
Call memory.maintain_memory() periodically (cron, background job, etc.):
memory.maintain_memory(weeks=1, decay_rate=0.02, quarantine_hours=24)
# β {"decayed": N, "stale": M, "promoted": K}- Decay: every active/stale record loses
decay_rateconfidence perweekselapsed. - Stale: records past
expires_atget markedstaleand excluded from retrieval. - Promote: quarantined records older than
quarantine_hoursare promoted toactive.
learnkit/
ββ core.py # orchestrator: @memory.agent_learn, prepare_run β finalize_run β post-process
ββ tool_tracker.py # ToolTracker β captures tool calls, tool-success gate, plan attach
ββ procedural.py # extract_procedure, task-signature / coverage matching
ββ replay.py # replay_plan β execute a captured procedure with 0 LLM calls
ββ adapters/react.py # run_react_agent β auto-replay ReAct runner
ββ drift.py # check_drift + golden-suite export (procedure regression)
ββ retriever.py # hybrid FTS5 + dense retrieval
ββ router.py # bounded retrieval plan (β€ 8 records / ~1,200 tokens)
ββ composer.py # compose_context β the injected prompt block
ββ distiller.py # MemoryDistiller (DSPy) β distills records
ββ memory_quality.py # storage gates, confidence, help/harm utility
ββ schemas/ # 7 typed record kinds (SkillRecord, FailureRecord, β¦)
ββ backends/ # SQLite (default) + Qdrant / Mem0 / Zep + registry
ββ adapters/ # LangChain, LangGraph, CrewAI, AutoGen, LlamaIndex, OpenAI
ββ cli.py # `learnkit maintain`, `learnkit skills export`
architecture/ # Mermaid diagrams (product map Β· agent runtime Β· storage Β· benchmark)
benchmarks/ # agentic suite + reproducible RESULTS.json / MATRIX.md
Docs/ # FastAPI server + React observability dashboard
examples/ # runnable demos (agent_learn_demo.py β offline, no key)
tests/ # pytest suite
Contributions are welcome β see CONTRIBUTING.md for the full guide (setup, structure, PR checklist, design principles).
git clone https://github.com/siddhu1716/LearnKit && cd LearnKit
python -m venv .venv && . .venv/Scripts/Activate.ps1 # bash: source .venv/bin/activate
pip install -e ".[dev]"
pytest -q # run the test suite (offline, no key)
ruff check learnkit tests # lint
pre-commit install # enable commit hooksThe observability dashboard (Docs/dashboard) builds with npm install && npm run build.
All diagrams live in architecture/:
| Diagram | Shows |
|---|---|
high_level_architecture.mmd |
colored, at-a-glance overview (screenshot-ready) |
product_architecture.mmd |
the whole system, subsystem by subsystem |
agent_runtime_flow.mmd |
one task: capture β match β replay/guide β gate β persist |
storage_lifecycle.mmd |
a record's lifecycle (quarantine β active β decay) |
benchmark_flow.mmd |
how the reproducible benchmark numbers are produced |
v1.0. The agent path (@memory.agent_learn) is the product: procedure
capture, exact-match replay (zero planning calls), and guided sibling reuse,
over a SQLite + FTS5 store with a React observability dashboard.
Reproduce the published numbers in one command (offline, from committed runs):
python -m benchmarks.make_results # regenerates benchmarks/RESULTS.json + MATRIX.mdAgentic matrix (trials=1, k=1, seed=7, temperature=0) β 3/3 models PASS
the playbook_effect β₯ 0.5 gate at equal task success:
| Model | Gate | Quality lift | LLM calls (pooled) |
|---|---|---|---|
Qwen2.5-14B-Instruct |
β PASS | +1.875 | 53 β 30 (β43%) |
Qwen2.5-32B-Instruct |
β PASS | +1.75 | 44 β 28 (β36%) |
Llama-3.3-70B-Instruct |
β PASS | +2.25 | 88 β 56 (β36%) |
Headline: +2.25 best quality lift Β· β38.4% pooled LLM planning calls Β· equal success.
See benchmarks/README.md to run against your own endpoints.
Apache-2.0 β see LICENSE and NOTICE. Β© 2026 LIA Labs.
Apache-2.0 is a permissive open-source license: commercial use is allowed. It adds an explicit patent grant and attribution/NOTICE requirements over MIT.