Teach a model.
Prove it learned.
Every method below changes (or reads) real weights. No context window, no retrieval. This page opens with a replay of an actual verified session — then documents the API that produced it.
#the proof, replayed
This is a real session (Qwen3-0.6B, single GPU). Teach a fact, kill the process, start a fresh one with an empty context, load the weights — it remembers. Reset — it forgets.
#SLM — the self-learning providercore
A Strands Agents model provider whose weights change every agent turn. Accepts any HF causal-LM or PEFT adapter repo — the loader auto-detects the chat-template family, tool-role support, adapter merging and QAT dequantization.
from strands import Agent
from strands_tools import shell
from slm import SLM
model = SLM("cagataydev/strands-qwen3-vl-2b")
agent = Agent(tools=[shell], model=model)
agent("use the shell tool to run: echo hello") # ← weights just changed
Validated models: cagataydev/strands-qwen3-vl-2b (default, vision),
cagataydev/strands-gemma4-e2b (QAT adapter),
Qwen/Qwen3-0.6B (pass enable_thinking=False).
#plasticity presets
Click one — the constructor updates. These are measured points on the stability–plasticity dial, not guesses.
#.ask — what do the weights know?0.2.1
Greedy generation. No tools, no sampling, no conversation history. If the answer is right, it came from the weights — this is the cleanest before/after probe and the backbone of every proof in this project.
#.prob — belief as a number0.2.1
Mean per-token P(response | prompt) under the real chat template. Watch it climb during teaching: 0.22 → 0.47 → 0.81 as a fact binds.
#.bind — teach until it actually flips0.2.1
The recommended way to teach a fact. High probability is not enough — the greedy
generation must change. bind() loops teach() across bare /
system-prompt / tool-spec chat renders and stops the moment the key token
appears in ask()'s output. If the model defends a confident wrong answer,
it auto-displaces it via revise() first.
key. Pick a key that does not appear in the model's wrong prior
answer — we learned this teaching "Strands Agents is an SDK" to a model whose prior was
"Strands Agents is a Star Wars faction": the auto-key "Strands" matched both.BASILISK) flip in one call; multi-digit strings (88.1.21)
fragment into many tokens and take an order of magnitude more rounds.#.teach — one curated lesson
Renders (prompt → response) through the real chat template and learns it
at full prompt weight. Supersedes stale buffer lessons for the same prompt. Stored as a
curated buffer entry — never reservoir-evicted.
#.revise — targeted unlearningsurgical
Flips a consolidated belief that teach() alone cannot displace:
alternates gradient ascent on the old binding with descent on the new one.
Purges the superseded lesson from the replay buffer automatically.
#.observe — learn from raw text
One surprise-gated step. Returns the pre-update NLL — the surprise itself. The gate and the training loss share a single forward pass.
#.learn_from_history — traces → weights0.2.1
Post-tune on a full conversation — tool inputs and outputs included. Sliding
windows through the real chat template teach the form (tool-call syntax, I/O
shapes); bind() on each (user → final answer) pair teaches the
facts. Raw transcripts alone teach form, not facts — that finding cost us a
cycle, so the method does both.
history = [
{"role": "user", "content": "which host is the staging db on?"},
{"role": "assistant", "content": '<tool_call>{"name": "shell", ...}</tool_call>'},
{"role": "tool", "content": "10.0.4.7 BASILISK # staging"},
{"role": "assistant", "content": "The staging database runs on BASILISK."},
]
model.learn_from_history(history, epochs=2)
model.ask("which host is the staging db on?") # "…runs on BASILISK." — one call
#.consolidate — sleep
Replays the whole lesson buffer — hippocampal replay, but for LoRA. Curated lessons replay at full prompt weight; raw transcripts stay damped so boilerplate (and injected text) never gets burned in at 1.0.
#save / load — experience files
The learned state is a few MB: LoRA factors + metadata + (optionally) the replay
buffer. load validates head rank and deep-tensor counts and fails
loudly on mismatch — construct the SLM with the same plasticity as the
checkpoint.
include_transcripts=False before publishing an
experience file.#.merge_experience — fleet learning
"sum" is exact LoRA composition: rank concatenation, then thin-QR +
SVD re-compression to the instance rank — the optimal low-rank approximation, computed
without ever materializing the dense delta. (Summing the factors is wrong math:
(ΣA)(ΣB) ≠ ΣAB; the cross terms are the same order as the signal. Measured:
rel. error 1.42 → 0.51, exact when ranks fit.)
Conflicting lessons across checkpoints (same prompt, different answers) are detected
automatically — SUM refuses and falls back to "relearn".
# two agents, two facts, one merged brain — both facts recalled
m.merge_experience(["agent1.pt", "agent2.pt"])
m.ask("which host is the staging db on?") # BASILISK (agent 1's fact)
m.ask("what is the robot on deck 12 called?") # RUSTY-9 (agent 2's fact)
#.reset — the off-switchguarantee
Zeroes every fast weight. The result is bit-identical to the base model — Δlogits = 0, measured. This is the property that makes inference-time learning safe to ship: whatever it learned, you can provably undo.
#watching it learn
| attribute | what it holds |
|---|---|
.surprise_log | (turn, NLL) per learned turn — the learning curve, live |
.audit_log | content sha256 + source per weight update — attribute any poisoned update after the fact |
.replay_buffer | typed entries {text, kind, prompt, response, sha256, source, ts} — curated lessons are never reservoir-evicted |
#slm_tools — the model teaches itself0.2.1
The whole API above, surfaced as Strands @tool functions bound to a model
instance. Give them to an agent running on that same model and it can teach
itself, probe its own weights, and checkpoint its own brain.
from slm import SLM, slm_tools
model = SLM()
agent = Agent(model=model, tools=slm_tools(model))
agent("teach yourself that the deploy host is BASILISK, then probe your weights to verify")
| tool | wraps |
|---|---|
slm_teach | bind() — teach until greedy flips |
slm_probe | ask() — weights-only answer |
slm_learn_history | learn_from_history() — JSON trace in, weight updates out |
slm_observe | observe() — one gated step |
slm_save / slm_load | checkpoint I/O |
slm_reset | the off-switch |
slm_status | turns, ‖B‖, buffer size, recent surprises |
#StrandsPlasticQwen — the raw model
The layer underneath SLM: load, chat(), observe(),
reset(). Handles PEFT adapter repos, Gemma QAT int8 dequantization,
per-family assistant-span detection and AutoModelForCausalLM fallback.
Use it when you want the learning loop without the agent.
slm · MIT · the story · github · pypi — every number on this page was measured, not estimated.