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.