cyrrus
Context routing for LLM calls.
Instead of sending a full static system prompt every time, cyrrus decides what context is relevant right now and sends only that.
from cyrrus import Projector
from cyrrus.providers import ollama
bot = Projector.minimal(llm_call=ollama("llama3.2"))
reply = bot.ask("hello")
That's it. No config required to start.
What it does
Every time a user sends a message, cyrrus:
- Routes — matches the message against your slides to find what's relevant
- Remembers — pulls stored facts about this user that relate to this message
- Packs — fits the relevant context into your token budget, highest priority first
- Sends — builds a proper messages array and calls your LLM
- Learns — extracts new facts from the conversation and stores them for later
The LLM receives clean, relevant context. Not a wall of everything you've ever defined.
Numbers
- 51.6% token reduction on average across mixed conversations (real tiktoken counts)
- 0.018 MB core install — zero required dependencies
- 121.9 MB / 241.7 MB RAM for the optional embeddings block
- 500 concurrent users with zero errors
- Surviving every adversarial test thrown at it
Install
pip install cyrrus # core, zero dependencies
pip install cyrrus[embeddings] # + semantic routing and memory
The import is always:
from cyrrus import Projector
Three ways to use it
Zero config
from cyrrus import Projector
from cyrrus.providers import ollama
bot = Projector.minimal(llm_call=ollama("llama3.2"))
reply = bot.ask("write me a python function")
With context routing
config = {
"core_lamp": {
"content": "You are a helpful assistant.",
},
"code_lens": {
"content": "Output only clean code blocks, no filler.",
"triggers": ["code", "script", "python", "function", "bug"],
},
"casual_lens": {
"content": "Keep it short and natural.",
"triggers": ["hey", "hi", "hello"],
},
}
bot = Projector(config, llm_call=ollama("llama3.2"))
reply = await bot.process("write a sorting function", session_id=str(user_id))
tokens and priority are optional — cyrrus fills them in automatically.
Individual components
from cyrrus import MemoryVault, IntentRouter, SlideTray
# Use any component standalone
memory = MemoryVault()
facts = await memory.retrieve("user_123", "what is my name", limit=3)
Providers
Ready-made wrappers for common APIs. Pass them directly as llm_call:
from cyrrus.providers import ollama, openai, anthropic, groq
# Local Ollama — free, no API key
bot = Projector(config, llm_call=ollama("llama3.2"))
# OpenAI
bot = Projector(config, llm_call=openai("gpt-4o-mini", api_key="sk-..."))
# Anthropic
bot = Projector(config, llm_call=anthropic("claude-haiku-4-5", api_key="sk-..."))
# Groq — fast, generous free tier
bot = Projector(config, llm_call=groq("llama-3.1-8b-instant", api_key="..."))
Or bring your own — any async function that takes a messages list and returns a string:
async def my_llm(messages: list) -> str:
response = await client.chat.completions.create(
model="my-model",
messages=messages,
)
return response.choices[0].message.content
bot = Projector(config, llm_call=my_llm)
Memory
cyrrus remembers things users tell it. No setup needed — the default extractor runs automatically:
User: "my name is Muratha" → stored: user_name = Muratha
User: "I'm building cyrrus" → stored: user_project = cyrrus
User: "I prefer Ollama" → stored: user_preference = Ollama
On the next message, relevant facts are retrieved and injected into the system prompt automatically.
With cyrrus[embeddings]: retrieval uses semantic similarity, so "what am I working on" finds user_project: cyrrus even without shared keywords.
Without: keyword overlap matching. Fast, zero dependencies, catches most cases.
Memory is per session_id. Facts never leak between users.
Conversation history
cyrrus maintains a sliding window of conversation history per session. The LLM always sees recent context, so follow-up questions work:
User: "what's the capital of France?"
Bot: "Paris."
User: "what's the population there?" ← LLM knows "there" means Paris
Bot: "Around 2.1 million in the city proper."
What cyrrus is not
- Not an agent framework. It controls input, never output.
- Not a vector database. Memory is SQLite with semantic retrieval optional.
- Not a replacement for LangChain. It's a layer, not a framework.
- Not magic. Keyword routing misses natural phrasing. Install
cyrrus[embeddings]if you need paraphrase matching.
session_id
This is important. Every user needs a unique session_id:
# Discord bot
reply = await bot.process(message.content, session_id=str(message.author.id))
# Web app
reply = await bot.process(user_input, session_id=request.session["user_id"])
# Single-user script — use ask() and don't worry about it
reply = bot.ask("hello")
Without a unique session_id, users share memory. That's a bug, not a feature.
Tracing
Every call stores a trace:
await bot.process("write a script", session_id="u1")
trace = bot.last_trace
print(trace["routed_slide_ids"]) # which slides matched
print(trace["memory_slide_ids"]) # which facts were retrieved
print(trace["dropped_slide_ids"]) # what didn't fit the budget
print(trace["messages"]) # exact messages sent to LLM
print(trace["stats"]["history_turns"]) # how many turns of history
License
AGPLv3. If you run cyrrus as part of a network service, you must make your modifications available. See LICENSE.
cyrrus — formerly known as slid3s
Metadata
Release files for cyrrus 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cyrrus-0.1.0.tar.gz | 51.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cyrrus-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 79.0 kB
Release files / cyrrus-0.1.0.tar.gz
| Download URL | cyrrus-0.1.0.tar.gz |
|---|---|
| Size | 51.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bbbf241ecd6931a5387a73d2a85462f028d2f1161da5c1b0f1aaf8424ecf8054
|
|
BLAKE2b-256 checksum How to use checksums |
0008ccdde3d19402247b5ba27bebcd6c8814de8f340ac5baad0e792ddde82985
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.5
|
Release files / cyrrus-0.1.0-py3-none-any.whl
| Download URL | cyrrus-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ad427ff0697d1b11666170233e8d5fa446d0299a5e83b1bd16afd80ec2325a16
|
|
BLAKE2b-256 checksum How to use checksums |
c4304c18f3fa83f77d5a17e78a8901d419571cfe40704f88a2126d1357d54b54
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.5
|