Memory types¶
Kioko has fifteen kinds of memory. Each one is an ordinary memory record: a memory_type, structured
metadata and, where it makes sense, a slot key. That has three consequences:
- every store supports every kind (in-process, SQLite, Postgres + pgvector, Qdrant, a remote service);
- every kind gets the same pipeline: secret filtering, de-duplication, version history, events, and the console's views;
- one
recall()can search every kind together.
Each kind has a typed manager on the memory object (memory.goals, memory.graph,
memory.working(session) ...). The managers use only the public memory API, so the same code runs on a local
MemoryLayer and on a RemoteMemory client.
| # | Kind | Accessor | Remembers | In the context | Recipe |
|---|---|---|---|---|---|
| 1 | Profile | memory.profile |
who the user is, one value per slot | always, About the user | 01 |
| 2 | Factual | memory.facts |
facts and preferences, with confidence | when relevant | 02 |
| 3 | Semantic | memory.semantic |
domain knowledge, shared or personal | when relevant, Background knowledge | 03 |
| 4 | Episodic | memory.episodes |
events with when / where / who / outcome | when relevant | 04 |
| 5 | Working | memory.working(session) |
the current task's scratchpad | always for the session | 05 |
| 6 | Conversational | memory.conversation(session) |
rolling summary + recent turns | the session summary | 06 |
| 7 | Goal | memory.goals |
objectives, progress, due dates | always, Open goals | 07 |
| 8 | Procedural | memory.procedures |
how-tos with versioned steps; standing rules | matching procedures; rules always | 08 |
| 9 | Skill | memory.skills |
reusable prompts, tools, code, with a track record | matching skills | 09 |
| 10 | Strategy | memory.strategies |
what worked for which kind of task | matching lessons | 10 |
| 11 | Decision | memory.decisions |
decisions with rationale and history | matching decisions | 11 |
| 12 | Graph | memory.graph |
entities and relations, with time | relations around named entities | 12 |
| 13 | KV cache | memory.kv |
cacheable prompt prefix, embedding and context caches | the stable prefix | 13 |
| 14 | Parametric | memory.parametric |
the bridge to fine-tuned weights | consolidated memories left out | 14 |
| 15 | Hybrid | memory.recall() / memory.hybrid |
one retrieval over every kind and representation | the whole block | 15 |
On top of the kinds sit pinned memories: hot memories in a Keep in mind section that always rides in the cacheable prefix (see intelligence). They are not a kind of their own, and any fact, knowledge item, procedure, skill, lesson or decision can be pinned.
You rarely need to call the managers yourself. memory.learn() / record_turn() fill the kinds from
conversation, and the intelligence also updates them: progress on goals, superseded
decisions, outcomes of procedures. The APIs below are for when your code knows something directly, or for tools
your agent calls.
All examples use user = "kumar" and from kioko import MemoryLayer; memory = MemoryLayer().
1. Profile memory¶
Stable facts about who the user is: name, location, employer, role, languages, manager, time zone. Each slot
holds one value. A new value replaces the old one, and the old one stays in the record's history. Profile
slots are ordinary memories keyed profile.<slot>. The rule extractor writes the same keys, so "I moved to
Hyderabad" from a conversation updates the same slot.
memory.profile.set("location", "Pune", user_id=user)
memory.profile.set("location", "Bengaluru", user_id=user) # updated, not duplicated
memory.profile.get(user_id=user) # {'location': 'Bengaluru'}
memory.profile.history("location", user_id=user) # previous values with timestamps
memory.profile.remove("phone_number", user_id=user)
Context: always included (About the user), and part of the cacheable prefix.
2. Factual memory¶
Durable statements about the user or their world ("User's team has 6 engineers") and preferences ("User prefers concise answers").
- A fact can carry a slot
key; a new value for the key updates it. - It can carry a
confidenceand be marked verified. - Contradictions supersede automatically: "doesn't like hiking anymore" replaces "likes hiking".
- Near-duplicates reinforce the existing memory instead of adding one.
memory.facts.add("User's manager is Krutika", key="work.manager", user_id=user, confidence=0.9)
memory.facts.add("User prefers metric units", preference=True, user_id=user)
memory.facts.verify(record, user_id=user)
memory.facts.search("units", user_id=user)
Context: the Relevant memories section, ranked by the query.
3. Semantic memory¶
General or domain knowledge: product facts, policies, definitions, runbooks. It is not about a user. By
default it lives in the shared namespace (@shared), which every user's context can draw on. Pass
user_id= to keep knowledge personal, or namespace= for a team or product scope. ingest() splits a
document into searchable chunks. Re-ingesting the same source updates the chunks instead of duplicating them.
memory.semantic.add("OPC UA servers listen on TCP port 4840 by default", topic="iiot") # shared
memory.semantic.ingest(open("runbook.md").read(), source="runbook-v3", topic="operations")
memory.semantic.search("default OPC UA port", user_id=user) # the user's own + shared knowledge
memory.semantic.forget_source("runbook-v2")
Context: Background knowledge, from the user's own knowledge and the shared base. Knowledge that the intelligence learns from a conversation stays in the user's own scope.
4. Episodic memory¶
What happened, when, where, with whom and how it ended. These are time-stamped experiences. Repeated events
are separate episodes (two gym visits are two episodes), so episodes are never de-duplicated. As they age,
consolidate() folds clusters of similar old episodes into summary memories. Faded episodes are forgotten:
the score is importance × recency × how often they were recalled.
memory.episodes.record("Presented the predictive-maintenance demo to the plant team", user_id=user,
when="2026-10-20", where="Pune plant", who=["Krutika"], outcome="approved for a pilot")
memory.episodes.timeline(user_id=user, since="2026-10-01")
memory.episodes.recent(user_id=user, days=7)
memory.episodes.consolidate(user_id=user, older_than_days=30)
Context: Relevant memories. For "when did ..." questions the planner asks for more of them and hybrid recall weighs recency.
5. Working memory¶
The scratchpad for the task at hand: the current task, intermediate results, ticket numbers, open questions.
- It is session-scoped.
- It is small:
capacity=10items; the least recently updated item is evicted first. - It expires:
ttl_secondsdefaults to 6 hours. - It is never embedded, so writing it costs nothing.
It never pollutes long-term memory unless an item is promoted.
wm = memory.working("shift-1", user_id=user)
wm.set("task", "Fix the E-217 fault on line 3")
wm.set("ticket", "INC-4521")
wm.snapshot() # {'task': ..., 'ticket': ...}
wm.promote("root_cause", memory_type="fact") # keep a conclusion long-term
memory.end_session("shift-1", user_id=user) # lasting slots -> an episode, then cleared
Context: always included for the session (Current task notes). The intelligence fills it from what the user says: the task, ticket and work-order numbers, error codes, links and file paths.
6. Conversational memory¶
The dialogue of one session, kept at constant size however long it gets: the last keep_last turns verbatim,
and everything older folded into a rolling summary (session:<id>:summary) once more than summarize_after
messages are not yet covered. The summary is written by the configured summarizer: extractive offline, or
LLMSummarizer(llm).
chat = memory.conversation("s1", user_id=user, keep_last=6, summarize_after=20)
chat.add_turn("How do I reset the relay?", "Switch off the breaker, then ...")
chat.as_messages(system="You are a plant support agent.") # summary + recent turns, ready for an LLM
chat.context(max_tokens=600)
Context: Earlier in this session (the summary). history_turns= adds the last turns verbatim.
7. Goal memory¶
What the user, or the agent, is working towards. A goal has:
- a status:
active,blocked,paused,doneordropped; - a priority:
low,medium,highorcritical; - progress from 0 to 1;
- an optional due date, success criteria, a reason, and a parent for sub-goals;
- an update trail.
Open goals are always in the agent's view, most important first, then the soonest due. Overdue goals are flagged.
goal = memory.goals.add("Ship Kioko 1.0", user_id=user, priority="high", due="2026-12-15",
criteria="published and installable")
memory.goals.add("Write the cookbook", user_id=user, parent=goal)
memory.goals.update(goal, user_id=user, progress=0.6, note="all kinds done")
memory.goals.complete(goal, user_id=user)
memory.goals.tree(user_id=user)
Context: always included (The user's open goals), and part of the cacheable prefix. The intelligence records progress ("I'm 60% done with ..."), completion ("I shipped ...") and dropped goals ("I gave up on ...") from conversation.
8. Procedural memory¶
How to do things. A procedure has named, ordered steps, an optional when to use, preconditions and tools.
Changing the steps creates a new version, and the old text stays in history. It also keeps a success and
failure record, so find() prefers procedures that work. Standing rules ("Always cite the ticket number")
are procedural memory at the prompt level: add_rule() stores them as instructions that are always applied.
memory.procedures.add("Reset the safety relay", user_id=user, when_to_use="the relay trips",
steps=["Switch off the main breaker", "Press reset for 5 seconds", "Switch the breaker on"])
memory.procedures.find("the relay tripped again", user_id=user)
memory.procedures.record_outcome("Reset the safety relay", success=True, user_id=user)
memory.procedures.add_rule("Always cite the ticket number", user_id=user)
Context: matching procedures in Procedures that may apply; rules in Instructions. When the user says "that worked" or "still broken", the intelligence credits the outcome to the procedure the agent just used.
9. Skill memory¶
Reusable capabilities: prompts, tools, code and workflows (kind="prompt" | "tool" | "code" | "workflow").
Each skill has a description, a body, inputs and a usage record (successes, failures, latency). find() ranks
by relevance × reliability. Reliability is the Wilson lower bound of the success rate, so 3 successes out
of 3 rank below 90 out of 100.
memory.skills.add("summarize alarm log", description="Top recurring faults from a PLC alarm log",
body="Summarize these alarms by fault code and count: {alarms}", user_id=user)
skill, score = memory.skills.find("summarise the alarms from line 3", user_id=user)[0]
memory.skills.render_prompt(skill, user_id=user, alarms="E-217 x12, E-301 x4")
memory.skills.record_use(skill, success=True, latency_ms=820, user_id=user)
Context: Skills you can use.
10. Strategy memory¶
Lessons about what works for which kind of task, in the style of Reflexion and ExpeL. Each
reflect(task_type, approach, outcome) updates the approach's track record (success, partial,
failure) and its lessons. recommend() returns approaches that worked, and avoid() returns those that
mostly failed.
memory.strategies.reflect("debugging drives", "check the encoder cable first", "success", user_id=user)
memory.strategies.reflect("debugging drives", "replace the drive", "failure", user_id=user,
lesson="expensive and rarely the cause")
memory.strategies.recommend("drive fault on line 3", user_id=user)
memory.strategies.avoid("drive fault on line 3", user_id=user)
Context: Lessons from earlier attempts. "Lesson learned: ..." in conversation becomes a strategy.
11. Decision memory¶
A decision log in the style of architecture decision records. Each decision has a title (the question), the
chosen option, the options considered, the rationale, the context, a status (proposed, accepted,
superseded or reverted), an outcome and a review date. A new decision on the same question
supersedes the old one, and the two are linked, so chain() shows how a decision evolved and a title
always resolves to the current decision.
memory.decisions.record("Vector store", chosen="Qdrant", options=["Qdrant", "pgvector"],
rationale="fast to set up", user_id=user)
memory.decisions.supersede("Vector store", chosen="pgvector", rationale="we already run Postgres", user_id=user)
[d.chosen for d in memory.decisions.chain("Vector store", user_id=user)] # ['Qdrant', 'pgvector']
memory.decisions.set_outcome("Vector store", "query latency halved", user_id=user)
Context: current decisions in Earlier decisions; superseded ones are left out. The intelligence turns "we switched from Qdrant to pgvector because ..." into a supersede.
12. Graph memory¶
A temporal knowledge graph of entities (people, teams, systems, places) and relations
(subject -predicate-> object).
- Single-valued predicates (
lives_in,works_at,reports_to,has_manager...) replace their old object and keep it as history with the period it was valid. - Entities can have types and aliases.
neighbors(),path()andabout()answer multi-hop questions.- With
auto_graph=True(on by default inkioko.intelligent()), relations are extracted from conversation.
memory.graph.add("Kumar", "reports_to", "Krutika", user_id=user)
memory.graph.add("Krutika", "leads", "AI & IIoT architecture", user_id=user)
memory.graph.add_entity("Krutika", type="person", aliases=["KK"], user_id=user)
memory.graph.about("Krutika", user_id=user)
memory.graph.path("Kumar", "AI & IIoT architecture", user_id=user)
memory.graph.relations(user_id=user, at="2026-01-01") # the graph as it was on a date
Context: Known relationships around the entities the query names. Hybrid recall also uses graph mentions as a ranking signal.
Memory links: the second layer. Graph memory also connects the memories themselves. Each new memory is linked to the memories it is related to, with a type and the evidence: a decision serves a goal, a lesson refines a procedure, a fix resolves an incident, an insight is derived from its episodes. The evidence is shared entities and rare words, meaning, a relation in the entity graph above ("Kioko uses pgvector" connects a pgvector decision to a Kioko goal), and the same session. Recall follows the links (spreading activation) and the context gains a How these memories connect section. The full algorithm, with formulas and worked examples, is in Graph memory.
memory.graph.links(memory_id) # [MemoryLink]: decision "use pgvector" serves goal "Ship Kioko"
memory.graph.related(memory_id, depth=2) # what is connected, best first, with the chain of links
memory.graph.explain(a, b) # the evidence, and whether the algorithm links them
memory.graph.link(a, b, type="resolves") # by hand (never fades)
memory.graph.connect(user_id=user) # link memories written before linking was on
Context: How these memories connect, the links among the memories in the block, plus up to links
memories brought in by their links (the recall plan sets how many).
13. KV-cache memory¶
Memory that saves compute rather than storing facts.
- Prompt-prefix caching.
memory.kvlays a request out with the stable part first: instructions, profile, goals and pinned memories, sorted deterministically, so the prefix is byte-identical from turn to turn. Provider prompt caches can then serve it. It has helpers for Anthropic (acache_controlbreakpoint, optional 1-hourttl) and OpenAI (stable system message first,prompt_cache_key). The same layout works with vLLM's prefix caching or llama.cpp'scache_prompt. - An embedding cache. Each distinct text is embedded once (
embedding_cache=4096). - A context cache. An identical
context()call withincontext_cache_ttlseconds (15) is served from memory, unless a write changed the user's memories in between.
prefix = memory.kv.prefix(user_id=user) # .text .hash .tokens .reused .sections
system = memory.kv.anthropic_system("Which PLCs do we use?", user_id=user, base_system=SYSTEM, ttl="1h")
messages = memory.kv.openai_messages("Which PLCs do we use?", user_id=user, base_system=SYSTEM)
memory.kv.stats() # prefix reuse, cache hit rates
14. Parametric memory¶
The bridge to model weights. Stable, much-used memories (facts, preferences, instructions, procedures,
skills, decisions, knowledge) can be exported as a fine-tuning dataset. Once a model has learned them, they
are marked as consolidated into it. context(model=...) then stops spending tokens on them. probe() checks
with the model which memories it really answers correctly.
rows = memory.parametric.export_dataset("train.jsonl", user_id=user, format="openai-chat")
memory.parametric.register_model("ft:plant-assistant-v1", base_model="<base model>", kind="lora", user_id=user)
memory.parametric.mark_consolidated(memory.parametric.candidates(user_id=user), model="ft:plant-assistant-v1")
ctx = memory.recall("What controllers does line 3 use?", user_id=user, model="ft:plant-assistant-v1")
15. Hybrid memory¶
One retrieval over every kind and every representation. recall() ranks candidates four ways:
- dense similarity of embeddings;
- sparse BM25 over the text;
- graph mentions of the entities in the query;
- recency, only for temporal questions.
It fuses the rankings with Reciprocal Rank Fusion (k = 60; weights dense 1, sparse 1, graph 0.8, recency 0.5),
splits the results into the context sections, and packs the most important items into a token budget (1200 by
default). The rest is listed in ctx.omitted, and a session summary that does not fit whole is shortened.
With intelligence on, a plan by intent sizes each section first.
ctx = memory.recall("How should I debug the OPC UA connection, and which vector store did we choose?",
user_id=user, session_id="s1", budget_tokens=800)
ctx.text # the prompt-ready block
ctx.counts() # items per section
ctx.omitted # what the budget left out
memory.hybrid.search("OPC UA certificate", user_id=user, limit=5) # the raw fused ranking, with signals
Where the ideas come from¶
- Working, episodic, semantic and procedural memory follow the CoALA framework for language agents (Sumers et al. 2023, arXiv:2309.02427).
- Episodic retrieval by recency, importance and relevance, and reflection: Generative Agents (Park et al. 2023, arXiv:2304.03442).
- The working context, recursive summaries and archival memory: MemGPT (Packer et al. 2023, arXiv:2310.08560).
- Strategies and lessons: Reflexion (Shinn et al. 2023, arXiv:2303.11366) and ExpeL (Zhao et al. 2023, arXiv:2308.10144).
- Skills as a reusable, verified library: Voyager (Wang et al. 2023, arXiv:2305.16291).
- The temporal knowledge graph with validity periods: Zep / Graphiti (Rasmussen et al. 2025, arXiv:2501.13956).
- Memory updates: Mem0 (Chhikara et al. 2025, arXiv:2504.19413).
- Linked, evolving notes: A-MEM (Xu et al. 2025, arXiv:2502.12110).
- Plaintext, activation (KV) and parametric memory: MemOS (Li et al. 2025, arXiv:2505.22101).
- Reciprocal Rank Fusion: Cormack, Clarke and Büttcher, SIGIR 2009.
- Decision records: Michael Nygard, "Documenting Architecture Decisions" (2011).
The intelligence that manages these kinds has its own list in intelligence.md.