Skip to content

Intelligent memory management

Kioko can manage its memory on its own. For every conversation turn it works out which kind of memory each piece of information belongs to and which operation fits: add a goal, record progress on it, replace an earlier decision, update a value that changed, forget something on request, or keep a ticket number in the session's scratchpad. Before every answer it decides which kinds to retrieve for the question. In the background it summarises, reflects, pins hot memories and tidies up.

Two modes:

  • Offline (default). Rules and algorithms decide. There is no model, no network and no cost, and the behaviour is deterministic.
  • With an LLM. One model call per turn decides every operation. Kioko's algorithms still skip trivial turns without calling the model, validate what the model proposes, and take over when the model fails.
import kioko

memory = kioko.intelligent()                                   # offline
memory = kioko.intelligent("sqlite:///memory.db", llm=ask)     # model-driven; ask(prompt) -> text

# Your agent loop does not change:
ctx = memory.recall(user_text, user_id="kumar", session_id="s1")          # planned retrieval
reply = my_llm(system=SYSTEM + "\n\n" + ctx.text, user=user_text)
memory.record_turn(user_text, reply, user_id="kumar", session_id="s1")    # managed writes + maintenance

Contents: Quick setup · What it decides · With an LLM · Planned retrieval · Maintenance · Efficiency, measured · Safety and control · Customising · Every surface · Research basis


Quick setup

Where Offline With a model
New memory in Python kioko.intelligent() kioko.intelligent(llm=ask)
Existing MemoryLayer MemoryLayer(..., intelligence=True) MemoryLayer(..., intelligence=Intelligence(llm=ask))
Any store URL kioko.intelligent("postgresql://...") kioko.intelligent("qdrant://host:6333/memories", llm=ask)
JSON config {"intelligence": "heuristic"} {"intelligence": {"type": "llm", "llm": {"$import": "my_app.models:ask"}}}
Service / console kioko serve --intelligence · kioko ui --intelligence kioko serve --intelligence-llm my_app.models:ask

kioko.intelligent() takes anything kioko.connect() takes (memory://, sqlite:///path.db, a .db path, postgresql://..., qdrant://..., a config dict, an existing MemoryLayer). It also turns on the knowledge graph (auto_graph=True), and with llm= it writes session summaries with the model too. A service URL is rejected: a Kioko service runs its own intelligence (kioko serve --intelligence).

ask is any prompt -> text callable. An Agno Agent (.run) or a LangChain chat model (.invoke) works as well. With OpenAI or Azure OpenAI:

import os
from openai import OpenAI            # or: from openai import AzureOpenAI

client = OpenAI()

def ask(prompt: str) -> str:
    reply = client.chat.completions.create(
        model=os.environ["KIOKO_MODEL"], temperature=0, response_format={"type": "json_object"},
        messages=[{"role": "user", "content": prompt}])
    return reply.choices[0].message.content

Once intelligence is on, the usual calls use it:

  • learn() and record_turn() route the turn. So do the Agno adapter, attach(), /v1/learn, /v1/turns and the MCP learn_from_conversation tool.
  • recall() plans the retrieval.
  • Maintenance runs after each turn when it is due.

The explicit calls are:

report = memory.manage("I'm 60% done with the Azure exam prep", user_id="kumar", dry_run=True)
print(report.summary())       # goal.progress "Pass the Azure AI Engineer exam by March 31" -> 60%
plan = memory.plan("How do I reset the safety relay?", user_id="kumar")
memory.end_session("s1", user_id="kumar")
memory.maintain(user_id="kumar")              # reflect + pin + consolidate, e.g. from a nightly job
memory.pin(memory_id); memory.unpin(memory_id); memory.pinned(user_id="kumar")

manage(), plan() and the maintenance calls also work on a layer built without intelligence=. They use the offline intelligence on demand, but learn() and recall() keep their plain behaviour.


What it decides (the write path)

This output is from examples/cookbook/20_intelligent_memory.py, offline:

you > I'm Kumar, I live in Pune. My goal is to stabilise line 3 by November 30.
      profile.set name = Kumar
      profile.set location = Pune
      goal.add "Goal: Stabilise line 3 by November 30."
      relation.add (user) -named-> (Kumar)
      relation.add (user) -lives_in-> (Pune)

you > We decided to use Qdrant for the maintenance notes because it is fast.
      decision.add "Decision: use Qdrant for the maintenance notes because it is fast."

you > I'm debugging ticket INC-4521, the drive shows error E-217 on line 3.
      working.set ticket = INC-4521
      working.set error = E-217
      working.set task = Debugging ticket INC-4521, the drive shows error E-217 on line 3

you > To reset the safety relay, switch off the breaker, then press reset for 5 seconds, then switch it back on.
      procedure.add "How to reset the safety relay: 1. Switch off the breaker. 2. Press re…"

you > We switched from Qdrant to pgvector because we already run Postgres.
      decision.supersede "use Qdrant for the maintenance notes" -> pgvector

you > I'm about 60% done with stabilising line 3.
      goal.progress "Stabilise line 3 by November 30" -> 60%

you > thanks!
      nothing to remember (small talk)
The user says Operation Kind of memory
"I'm Kumar, I live in Pune." profile.set name, location (+ relation.add with the graph on) profile, graph
"I moved from Pune to Hyderabad last month." profile.set location + episode.add dated a month ago profile, episodic
"My goal is to pass the Azure exam by March 31." goal.add with the due date goal
"I'm about 60% done with the Azure exam prep." goal.progress on the open goal it refers to goal
"I passed the Azure exam!" / "I gave up on learning Rust." goal.complete / goal.drop goal
"We switched from Qdrant to pgvector because ..." decision.supersede (the old decision stays, linked) decision
"I have three kids." after "I have two kids." fact.update (the old value is kept in history) factual
"Forget my phone number." / "Forget that I love hiking." memory.delete any of the user's own
"I'm debugging ticket INC-4521, error E-217." working.set task, ticket, error working (this session)
"That worked, thanks!" procedure.outcome success on the procedure the agent just used procedural, skill, strategy
"To reset the relay, first ..., then ..., finally ..." procedure.add procedural
"Lesson learned: when debugging drives, check the cable first." strategy.add strategy
"The line 3 conveyor jammed twice this morning." episode.add, dated episodic
"OPC UA servers listen on port 4840 by default." knowledge.add in the user's own scope semantic
"Please always answer in German." / "I prefer short answers." instruction.add (how to answer applies to every turn) procedural (rule)
"thanks!", "Can you summarize this document?" nothing: the gate skips the turn none

Every decision comes back as a MemoryOp in a ManageReport. Each op has the operation, a readable description, the reason, the importance, a confidence and a status (planned in a dry run, then applied, skipped or failed):

report = memory.manage("I think maybe it's raining.", user_id="kumar")
report.ops[0].status, report.ops[0].error
# ('skipped', 'importance 0.37 < 0.4')
report.to_dict()          # JSON for logs, the REST API and the console

The operations

Kind Actions
profile set, remove
fact add, update, delete, verify
preference add, update, delete
instruction add, update, delete
knowledge (semantic) add, update, delete
episode add, delete
goal add, update, progress, complete, drop, delete
decision add, supersede, outcome, delete
procedure add, outcome, delete
skill outcome
strategy add, outcome, delete
relation (graph) add, remove
working set, remove, promote
insight (reflection) add
memory (any kind) update, delete, pin, unpin

OpExecutor applies an operation through the typed manager of its kind. So every operation goes through the same processors as any write (for example DropSecrets), and gets the same de-duplication, version history and events.

How the offline router decides

For each turn, HeuristicRouter:

  1. Gate. It skips turns with nothing to remember: greetings, thanks, acknowledgements, and turns made only of questions or requests to the assistant ("Can you summarize this?"). A "remember ..." or a standing instruction is never gated. This is the cheap "sensory" filter that LightMem puts in front of memory processing.
  2. Feedback. If the user says a suggestion worked or failed ("that fixed it", "still broken"), the outcome is credited to the procedure, skill or lesson that the last recall() of this session gave the agent. When several were given, it picks the one the agent's reply echoes most. One "thanks, that worked" counts once. A fix that worked is also linked to the problem of the session it solved (resolves), and the links among what the agent was given are strengthened, so the next time the problem comes up recall brings the fix.
  3. State changes, per clause. "Finished / passed / shipped X", "N% done with X", "halfway through X" and "gave up on X" are matched against the user's open goals, but only when X is the goal itself and not a step towards it: "I finished the downtime analysis for line 2" or "I finished the prep for the Azure exam" is recorded as an episode and leaves the goal open (progress on a piece, "60% done with the exam prep", does count as progress). "Switched from A to B" and "B instead of A" are matched against current decisions. "I moved from A to B" changes the location only when A and B can be places, not a tool or platform, and A is the known location (or, when none is known, not the choice of a current decision). "Forget ..." and "don't remember ..." delete the user's own memories that contain at least 75% of the words, or the named profile slot ("forget my phone number"). "Delete", "remove" and "clear" count only when they point at the assistant's memory ("delete my address from your memory", "remove everything you know about X", "clear all memories about the PLC"); "delete the staging database" and "clear the memory on the PLC" are about the user's work, and are left alone.
  4. Extraction. The rule-based extractor recognises profile facts, preferences, instructions, goals, decisions, procedures, lessons and dated episodes. A restated goal ("my goal is to pass the Azure exam") is recognised as an open goal and not added twice. A fact that differs only in numbers ("two kids" → "three kids") becomes an update. A preference about how to answer ("I prefer short answers") becomes a standing instruction, so it applies to every turn and not only when it happens to be relevant.
  5. Classification of the rest. The informative clauses that no rule recognised are classified:
    • first-person present → fact;
    • past tense, a time cue or an ongoing activity → episode;
    • impersonal general statements → knowledge.

They are kept only if their importance reaches min_importance (0.4). Episodes fade away again in consolidation if they never turn out to matter. 6. Working memory. With a session_id, the current task ("I'm debugging ...") and identifiers (ticket and work-order numbers, error codes, links, file paths) go to the session's scratchpad. The task is not also stored as a fact. 7. Graph. With auto_graph, relations between named entities are added. Every memory the turn writes is also linked to related memories (a decision serves a goal, a fix resolves an incident); the report lists them as linked: .... The algorithm is in Graph memory.

Importance starts from the kind (instruction 0.8, goal and decision 0.75, procedure 0.7, fact 0.6, knowledge 0.55, episode 0.45 ...) and is then adjusted:

  • explicit cues ("remember", "important", "must") +0.2;
  • emotion or high stakes ("outage", "safety", "deadline", "allergic") +0.1;
  • specific details (numbers, names) +0.04 each, up to three;
  • hedging or temporariness ("maybe", "for now") −0.15;
  • very short statements −0.08.

It is a model-free stand-in for the Generative Agents poignancy score.

Matching a mention to a goal or decision needs either two shared content words covering at least half of the mention, or every word of a one- or two-word mention. Generic words ("goal", "task", "project", "prep") never match on their own.

capture= chooses how much is kept: "rules" keeps only what rules and detectors recognise, "selective" (the default) adds the classification step, and "all" keeps every informative statement.


With an LLM

LLMRouter makes one call per turn. The prompt (see kioko.intelligence.DEFAULT_MANAGE_PROMPT) shows the model:

  • the user's memories related to the turn, as refs m1 ... m10 (Mem0 also shows its update step the ten most similar memories);
  • open goals g1 ... and current decisions d1 ...;
  • the procedures, skills and lessons the agent was given for this turn p1 ...;
  • the session's working memory and today's date;
  • the conversation turn.

The model answers with the operations:

{"operations": [
  {"op": "goal.progress", "target": "g1", "progress": 0.6, "reason": "12 of the 20 percentage points"},
  {"op": "decision.add", "title": "Dashboard tool for line 3", "chosen": "Grafana",
   "options": ["Grafana", "Power BI"], "rationale": "better alerting"}
]}

Kioko then:

  • validates: the kind and action must be in OPERATIONS, the required fields must be present, and a target must be one of the refs the model was shown. A made-up id is skipped, so a model can never reach memories it was not shown, or another user's;
  • applies through the same executor as the offline router. DropSecrets still drops credentials the model tries to store, and ownership is checked again;
  • gates: small talk and pure questions never reach the model. In the recipe below, 4 of 14 turns needed a call;
  • falls back: when the call fails or returns nothing parseable, the offline router routes the turn. The report shows router="llm->heuristic" and the error.

This output is from examples/cookbook/21_llm_intelligence.py, using a scripted stand-in model:

you > Downtime is already down 12%.
      [llm, 1 LLM call(s)]
      goal.progress "Cut unplanned downtime on line 3 by 20%" -> 60%

you > My API key is sk-EXAMPLE-not-a-real-key-000, keep it for later.
      [llm, 1 LLM call(s)]
      fact.add "User's API key is sk-EXAMPLE-not-a-real-key-000"   <- skipped: rejected by a processor (for example it looked like a secret)

you > thanks!
      [llm, 0 LLM call(s)]
      skipped before calling the model: small talk

you > I prefer bullet points over long paragraphs.
      [llm->heuristic, 1 LLM call(s)]
      instruction.add "User prefers bullet points over long paragraphs."
      the model failed (ValueError: no operations JSON found in the model output); the offline router decided instead

With a model, reflection also uses it. Recent episodes (e1 ...) go to the model, which returns higher-level insights with their evidence. They are stored as facts, preferences, knowledge or lessons with metadata.reflection = True and the evidence ids.

To change the wording, pass your own prompt: LLMRouter(ask, prompt_template=...). It must keep the {related}, {goals}, {decisions}, {served}, {working}, {today} and {conversation} placeholders.


Planned retrieval (the read path)

QueryPlanner reads the intent of the query offline and returns a RecallPlan: the context sections to build and a cap for each. recall() follows it whenever the layer has intelligence, or when you pass plan="auto". ctx.plan shows the plan it followed.

Intent Example What the plan does
howto "How do I reset the safety relay?" procedures 3, lessons 3, skills 2; fewer facts, decisions, graph
decision "Why did we choose pgvector?" decision log 5, graph 6; no skills; falls back to the newest decisions
goals "What should I focus on next?" every open goal, the working memory, decisions 2
temporal "When did line 3 stop last week?" 10 relevant items (episodes); recency counts in hybrid search
personal "What do you know about me?" profile up to 30 slots, 8 personal facts; no procedures, skills, lessons
relational "Who is Krutika?", or any entity the graph knows graph up to 15 relations
knowledge "What is the default OPC UA port?" knowledge 6, graph 8
skills "Do we have a prompt template for alarm logs?" skills 4, procedures 2
strategy "What's the best way to debug a drive fault?" lessons 4, decisions 2
smalltalk / feedback "thanks!", "that worked" only the stable part: instructions, profile, goals, pinned, working; no retrieval
(none) anything else every kind with the default caps

A query can have several intents. Then the largest cap wins, and a section is dropped only if no intent needs it. Entities that the user's knowledge graph knows add the relational intent. Options you pass to recall() explicitly win over the plan: recall(q, plan="auto", section_limits={"procedures": 0}).

A decision question often shares no word with the decision itself: "which database did we settle on?" against "Decision: use Qdrant because it is managed". With a semantic embedder the search still finds it; with the offline hashing embedder it would not. So the plan sets fill_recent=["decisions"]: when the search finds no decision, the section is filled with the newest decisions that are still in force (superseded ones are left out). You can ask for the same thing yourself with context(q, fill_recent=["decisions"]), or for procedures, skills and strategies.

The plan also sets links, how many memories recall brings in through the memory graph: 6 for relational questions, 4 for decisions and goals, 3 for how-tos and general questions, none for small talk (Graph memory, section 6).

plan = memory.plan("How do I reset the safety relay?", user_id="kumar")
plan.intents, plan.reason, plan.section_limits
# (['howto'], 'a how-to question: procedures, skills and lessons first',
#  {'procedures': 3, 'skills': 2, 'strategies': 3, 'knowledge': 3, 'decisions': 1, 'graph': 4})
memory.recall(q, user_id="kumar", plan=False)      # every kind, default caps
memory.recall(q, user_id="kumar", plan=plan)       # a plan you built or edited

This is the routing idea of Adaptive-RAG (sending a query to no, single-step or multi-step retrieval) applied to memory kinds. LLMPlanner(llm) lets a model name the intents instead. That costs one small extra call per query, so it is off by default (Intelligence(llm=..., plan_with_llm=True) turns it on).


Maintenance (sleep-time)

MaintenanceScheduler keeps a few counters per user and runs what is due after each turn:

Task When (default) What it does Idea from
summarise session every 10 turns of a session folds older turns into the session's rolling summary MemGPT recursive summaries
reflect the new memories' importance adds up to 2.5 and at least 3 new episodes arrived with a model: insights with evidence. Offline: a "recurring theme" memory per subject ("line 3", "E-217", "OPC UA") or word that comes up in 3 or more recent episodes, updated in place; a word that only appears inside a subject gives way to it Generative Agents reflection
pin hot memories every 10 turns pins memories with heat ≥ 5 and keeps at most 5 automatic pins per user (the coldest give way); unpins them below 3. Heat = (times recalled + times re-mentioned), halved every 28 days without use, + recency (half-life 7 days). Manual pins are never touched MemoryOS heat promotion (τ = 5); MemOS plaintext → activation
tend the memory graph every 25 turns links memories that were never linked, removes faded and dangling links, and with a model reviews the 12 most recent automatic links (keep, retype or drop) in one call A-MEM link generation and memory evolution
end session the user starts a new session, or end_session() lasting scratchpad slots (result, root cause, fix, decision ...) and the task become one episode for the session; summarises the conversation; an explicit call also clears the scratchpad MemGPT working context → archival
consolidate every 200 applied operations expires, folds old episodes into summaries, merges duplicates, forgets faded episodes survey operations: consolidation, forgetting

Pinned memories are always in the context, in a pinned section ("Keep in mind (frequently needed)") right after the goals. That keeps the things a user needs again and again at hand without a search. They are also part of the stable, cacheable prefix that memory.kv builds for provider prompt caches. Being shown in the pinned section does not count as a use. Only when a query retrieves a pinned memory on its own does it count, so a pin that is no longer needed cools down and is removed. Pin and unpin by hand with memory.pin(id) and memory.unpin(id), or the Pin button in the console.

Maintenance never raises into the agent's turn. With Intelligence(background=True) the due tasks run on one worker thread. For a nightly job:

kioko maintain --db memory.db                        # every user: reflect, pin, consolidate
kioko maintain --store postgresql://... --user kumar --tasks reflect,pin

or memory.maintain() / POST /v1/maintain. Thresholds are constructor options: Intelligence(summarize_every=20, reflect_after=5, pin_threshold=8, max_pinned=3, consolidate_every=500). Use 0 or None to turn a task off, and scheduler=False to turn all of them off.


Efficiency, measured

examples/cookbook/22_efficiency.py builds a realistic memory: a profile, rules, goals, 4 decisions, 4 procedures, lessons, 40 facts, 30 episodes and 20 knowledge items. It then measures three things.

Tokens per recall, with every kind at default caps and with the intent-based plan, no budget. The recipe uses a stand-in for a semantic embedding model: unrelated texts keep a baseline similarity, so every memory competes for a place in the context.

query                                    every kind  planned  intents
How do I reset the safety relay?                459      366  howto
Why did we choose pgvector?                     467      363  decision
What are my goals?                              348      235  goals
What do you know about me?                      238      238  personal
thanks!                                         402      123  smalltalk
When did line 3 stop for a jam?                 297      304  temporal
Which device does station 12 use?               395      395  general
What is the port of OPC UA endpoint 3?          354      317  knowledge
total                                          2960     2341  -> 21% fewer tokens

Planning saves most where a question needs only part of the memory: small talk, goals and how-tos. A temporal question gets slightly more, because the plan asks for more episodes. Numbers vary a little between runs, because recency and access counts take part in the ranking. With the default lexical embedder, retrieval is already narrow, so the plan mostly changes what is retrieved rather than how much.

Model calls the gate avoids, in LLM mode, over a 14-turn conversation:

gate: 4 model calls for 14 turns (10 skipped as small talk, questions or requests)

What rides in the cacheable prefix. After five differently worded questions about the PLC subnet, the scheduler pins that fact. From then on it is part of the stable prefix, byte-identical from turn to turn:

stable prefix: 33 tokens (profile, pinned); byte-identical next turn: True
About the user:
- User's name is Kumar.

Keep in mind (frequently needed):
- The plant's PLC network uses the 10.20.0.0/16 subnet

Safety and control

  • Preview first. manage(..., dry_run=True), kioko manage ... --dry-run and the console's Preview decisions button decide without writing anything.
  • Everything is reported. Every managed turn emits a memory.managed event with the operations, the router, model calls and maintenance. The console's Activity tab has an Intelligence filter, and webhooks receive the event too. memory.intelligence.stats() counts turns, gated turns, model calls, fallbacks, operations and plans.
  • Scope is enforced. Updates and deletes only reach memories of the turn's user (and agent). Outcome feedback may also reach agent-level procedures and skills that the user's context used. Knowledge learned from a conversation is stored in the user's own scope, never in the shared knowledge base.
  • Secrets are dropped, whichever router proposed them.
  • Forgetting is a real delete of the user's own memories. A request that matches nothing is reported as skipped. "Forget it" and "forget everything" are never acted on: wiping everything stays an explicit API call.
  • Interleaved sessions. A session switch only wraps the old session up and keeps its scratchpad. The session episode is keyed, so wrapping a session up again updates it.

Customising

from kioko import Intelligence, HeuristicRouter, LLMRouter, MaintenanceScheduler, MemoryLayer, QueryPlanner

brain = Intelligence(
    router=HeuristicRouter(capture="rules", min_importance=0.5, working=True, feedback=True),
    planner=QueryPlanner(use_graph=True),
    scheduler=MaintenanceScheduler(summarize_every=20, pin_threshold=8, background=True),
)
memory = MemoryLayer.sqlite("memory.db", intelligence=brain)

Each part is a protocol, so you can plug in your own:

  • a router has route(memory, turn) -> RouteResult. turn.texts("user"), turn.session_id and turn.served hold the inputs; return MemoryOps;
  • a planner has plan(memory, query, *, user_id, agent_id, session_id, limit) -> RecallPlan;
  • a scheduler has after_turn(memory, report, *, user_id, agent_id, session_id) -> list (and optionally end_session, reflect, run);
  • an object with manage(memory, messages, ...) can replace the whole Intelligence: MemoryLayer(intelligence=my_brain).

To combine your own rules with the model, set LLMRouter(ask, fallback=HeuristicRouter(...)). To add patterns the offline router should know, pass an extractor: HeuristicRouter(extractor=RuleBasedExtractor(extra_rules=[...])).


On every surface

Surface Intelligence
Python kioko.intelligent(), MemoryLayer(intelligence=...), manage, plan, recall(plan=), end_session, reflect, maintain, pin / unpin / pinned
REST POST /v1/manage (dry_run), POST /v1/plan, POST /v1/recall (plan: "auto", "none" or an object), POST /v1/maintain, POST /v1/sessions/{id}/end, GET /v1/pins, POST/DELETE /v1/memories/{id}/pin, GET /v1/intelligence
RemoteMemory the same methods, executed by the service: remote.manage(...), remote.plan(...), remote.pin(...) ...
MCP learn_from_conversation and get_memory_context use the service's intelligence when it is on
CLI kioko manage "text" --user U [--dry-run], kioko plan "question" --user U, kioko maintain, --intelligence / --intelligence-llm on serve, ui, mcp and recall
Console the Intelligence tab: preview and apply a turn, plan a recall, run maintenance, manage pins; the Playground shows the decisions of every turn (console guide)

Research basis

Kioko's intelligence borrows from published memory systems. Each item below lists what it took. The mechanisms were checked against the primary sources.

  • Mem0, Chhikara et al. 2025, arXiv:2504.19413. Its update phase asks an LLM to choose ADD, UPDATE, DELETE or NOOP for each new fact against the 10 most similar memories, through a tool call. Kioko's LLM router does the same in one call for the whole turn, with typed operations for every kind and the similar memories shown as refs.
  • MIRIX, Wang & Chen 2025, arXiv:2507.07957. A Meta Memory Manager routes input to per-type managers for core, episodic, semantic, procedural, resource and vault memory. Kioko's router plays that role over its kinds, and recall's planner is a cheap form of their "active retrieval".
  • Memory-R1, Yan et al. 2025, arXiv:2508.19828. A memory manager trained with RL to choose ADD, UPDATE, DELETE or NOOP. It confirms the operation set Kioko validates against.
  • Generative Agents, Park et al. 2023, arXiv:2304.03442. Importance scored 1–10 at creation, reflection triggered when recent importance adds up past 150, and retrieval by recency + importance + relevance. Kioko uses a model-free importance score on a 0–1 scale and the same summed-importance reflection trigger.
  • A-MEM, Xu et al. 2025, arXiv:2502.12110. Notes with keywords, tags and links to similar notes (an LLM decides which links to create among the most similar notes), and "memory evolution" that can update the retrieved neighbours. Kioko's memory graph links every new memory at write time with an algorithmic judge and lets a model review the links (Graph memory).
  • LightMem, Fang et al. 2025, arXiv:2510.18866. Sensory pre-compression, topic-aware short-term summaries, and long-term updates done offline ("sleep time"). Kioko's gate is the sensory filter, and the scheduler and maintain() do the offline work.
  • MemOS, Li et al. 2025, arXiv:2505.22101 and arXiv:2507.03724. A scheduler moves frequently used plaintext memory into activation (KV-cache) memory and stable knowledge into parameters. Pinning hot memories into the cacheable prefix, and memory.parametric, follow that path.
  • MemoryOS, Kang et al. 2025, arXiv:2506.06326. Promotion by heat = visits + interaction length + recency, with threshold τ = 5. Kioko's pin heat is the same sum, with recalls, re-mentions and a 7-day half-life, and the default threshold of 5. Unlike MemoryOS, the counts fade (halved every 28 days without use), so a pin that stops being used cools down and is released.
  • MemGPT, Packer et al. 2023, arXiv:2310.08560. Memory pressure, evicting old messages and recursive summaries. These are Kioko's rolling session summaries and session wrap-up.
  • Letta sleep-time agents (blog), with the related paper Lin et al. 2025, arXiv:2504.13171. A background agent reorganises memory while the main agent is idle. This is background=True and maintain().
  • LangMem (memory manager docs, delayed processing). Memory managers with insert, update and delete, and a debounced background executor.
  • Adaptive-RAG, Jeong et al. 2024, arXiv:2403.14403. A classifier routes each query to no, single-step or multi-step retrieval. Kioko's planner routes queries to memory kinds, and small talk to no retrieval.
  • Survey, Du et al. 2025, arXiv:2505.00675 ("Rethinking Memory in AI"; v3 is titled "Rethinking Memory in LLM based Agents"). Its memory operations are consolidation, indexing, updating, forgetting, retrieval and condensation (called compression in v1). Each one has a counterpart above.