Skip to content

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 confidence and 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=10 items; the least recently updated item is evicted first.
  • It expires: ttl_seconds defaults 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, done or dropped;
  • a priority: low, medium, high or critical;
  • 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() and about() answer multi-hop questions.
  • With auto_graph=True (on by default in kioko.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.

  1. Prompt-prefix caching. memory.kv lays 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 (a cache_control breakpoint, optional 1-hour ttl) and OpenAI (stable system message first, prompt_cache_key). The same layout works with vLLM's prefix caching or llama.cpp's cache_prompt.
  2. An embedding cache. Each distinct text is embedded once (embedding_cache=4096).
  3. A context cache. An identical context() call within context_cache_ttl seconds (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.