Skip to content

Run it as a service

export KIOKO_API_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
kioko serve --db /data/memory.db --host 0.0.0.0 --port 8765 --intelligence
REST API            http://localhost:8765/v1   (OpenAPI: http://localhost:8765/v1/openapi.json)
MCP endpoint        http://localhost:8765/mcp

The agent loop is the same in every language: ask for memory before the LLM call, report the turn after it.

H='Authorization: Bearer '$KIOKO_API_KEY
curl -s localhost:8765/v1/recall -H "$H" -H 'Content-Type: application/json' \
  -d '{"user_id": "kumar", "session_id": "s1", "query": "Plan my weekend"}'     # -> {"text": "<memory>...", ...}
curl -s localhost:8765/v1/turns -H "$H" -H 'Content-Type: application/json' \
  -d '{"user_id": "kumar", "session_id": "s1", "user_message": "I moved to Pune", "assistant_message": "Noted!"}'
Endpoint Purpose
POST /v1/recall · POST /v1/context Everything the agent should know for a query: a prompt-ready text plus every section (hybrid, planned, within budget_tokens)
POST /v1/turns · POST /v1/learn After a reply: store the exchange and learn from it
POST /v1/manage · POST /v1/plan · POST /v1/maintain · GET /v1/intelligence The intelligence: decisions for a turn (dry_run), recall plans, maintenance, status
POST /v1/kinds/{kind}/{operation} · GET /v1/kinds · GET /v1/graph Every typed operation (goals/add, decisions/supersede, graph/neighbors ...), counts, the graph
POST /v1/search · POST /v1/memories · GET · GET/PATCH/DELETE /v1/memories/{id} · DELETE /v1/memories?user_id= Search, create (through the write pipeline), list, read, edit, delete, clear one user
GET /v1/pins · POST/DELETE /v1/memories/{id}/pin Pinned, always-at-hand memories
GET/POST /v1/memories/{id}/links · DELETE .../links/{target} · GET .../links/{target}/explain · GET /v1/memories/{id}/related · GET /v1/graph/memories · POST /v1/graph/connect The memory graph: links, linking by hand, evidence, spreading activation, the graph for drawing, linking what was never linked
POST /v1/messages · GET /v1/sessions · GET /v1/sessions/{id}/messages · POST .../summarize · POST .../end · DELETE /v1/sessions/{id} Sessions and short-term memory
POST /v1/consolidate · GET /v1/cache · GET /v1/events?after=<seq> · GET /v1/users · GET /v1/stats · GET /v1/info Maintenance, caches, activity log, users, counts, configuration
GET /v1/health · GET /v1/openapi.json Liveness and the OpenAPI 3.0 document (no key needed)

Import openapi.json (kioko openapi --server-url https://memory.example.com > openapi.json) into Postman, Swagger UI, Azure API Management or a Power Platform custom connector, or generate a typed client. Errors are JSON: {"error": "...", "code": "rejected"}, with 422 when a processor refused the content (for example a secret).

Security

Setting Behaviour
API key KIOKO_API_KEY (or --api-key-env NAME). /v1 and /mcp then require Authorization: Bearer <key> or X-API-Key: <key>, compared in constant time. health and openapi.json stay open.
Public bind Without a key, serve refuses any non-loopback address unless you pass --insecure.
DNS rebinding Without a key, or with the console on, requests need an expected Host header (add names with --allowed-host).
CSRF and Origin A request carrying an Origin header must be same-origin or from an allowed host. Origins listed with --cors-origin are also accepted, with CORS, on /v1 and /mcp only; the console never accepts cross-origin calls. --cors-origin '*' requires an API key.
Request limits JSON bodies only, at most 1 MB, and a 30-second socket timeout.

One key grants access to every user's memories, which suits service-to-service calls. Put a gateway in front for per-user auth, and use HTTPS outside a private network.

RemoteMemory and connect()

from kioko import connect, attach

memory = connect("http://memory-service:8765")   # RemoteMemory; API key from $KIOKO_API_KEY
memory.goals.add("Ship v1", user_id="kumar")      # every kind works remotely
memory.manage("We switched to pgvector", user_id="kumar", dry_run=True)    # so does the intelligence
agent = attach(my_agent, memory)                  # every adapter works remotely

RemoteMemory has the MemoryLayer API and return types (MemoryRecord, SearchResult, WriteResult, MemoryContext, ManageReport, RecallPlan ...). Rejected writes return None, a rejected update raises MemoryRejected, and an unknown id gives None / KeyError. Other failures raise RemoteMemoryError (.status, 0 when the service can't be reached). Idempotent requests are retried. Redirects are not followed, so the key never leaves for another host. subscribe() polls the activity log. Calls that act on memories need a user_id.

connect(target) accepts:

Target Gives you
None $KIOKO_URL, else an in-process layer
"memory://" an in-process layer
"sqlite:///path.db", or a .db / .sqlite path a local SQLite layer
"postgresql://..." a layer on Postgres + pgvector
"qdrant://host:6333/collection" (qdrant+https:// for TLS) a layer on Qdrant
"http(s)://..." RemoteMemory
a .json path or a config dict MemoryLayer.from_config

kioko.intelligent(target, llm=None, ...) takes the same targets (except a service URL) and returns a self-managing layer. Anything with the same methods also satisfies the MemoryAPI protocol, so you can bridge another backend.