Synrix: State Infrastructure for Autonomous Agents
Your agent runs at machine speed. Its memory should too.
The problem
Every AI agent framework eventually hits the same wall: the agent's reasoning is fast, but its memory isn't. Vector databases require embedding models. SQL databases serialize every write through a query planner. Key-value stores give you no structure. In-memory dicts don't survive a crash.
None of these were designed for an agent that fires 10,000 state updates per second and cannot afford to wait.
Synrix was.
What Synrix is
A flat, memory-mapped array of fixed-size, cache-aligned nodes — what we call a lattice. Every node is reachable in O(1) by ID. Every prefix group is reachable in O(k) where k is the number of matching results, regardless of total dataset size. No query planner. No embedding model. No network round-trip in the direct access path.
The name is literal: the file is a dense, binary-encoded grid of fixed-size structs. No linked lists. No B-trees. No dynamic allocation. The offset of any node is arithmetic — multiply the ID by the node size, seek, read. That is what makes O(1) lookup real rather than amortized.
The engine is written in C. It runs on the same machine as your agent. It uses mmap so reads hit RAM, not disk. Writes go through a WAL before they hit the file, so a SIGKILL mid-write loses nothing. The node layout is fixed — 1216 bytes, 64-byte cache-aligned — so the CPU prefetcher works with you, not against you.
This is not a general-purpose database. It is purpose-built state infrastructure for agents that need to read and write structured state at frequencies no human will ever reach.
Design decisions and why
| Decision | Reason |
|---|---|
| Fixed 1216-byte nodes | Predictable layout → CPU prefetch, O(1) offset calculation, no fragmentation |
| Prefix-only queries | Agents design their own namespace at init time; O(k) with zero schema overhead |
| No deletion | Agents append state; a learning system that deletes its own memory is broken |
| No multi-op transactions | Single-operation atomicity is sufficient for append-only agent state |
parent_id stored on disk |
Agents build reasoning chains; the hierarchy persists across restarts |
| 512-byte data field | Agent state is structured and small: confidence scores, patterns, outcomes |
| WAL + fsync | Agent processes get killed; state must survive it |
| No embeddings | Agents know what they're looking for; semantic search is a human convenience |
mmap + direct C access |
The Python layer is optional; raw access is sub-microsecond |
What an agent loop looks like with Synrix
from synrix.raw_backend import RawSynrixBackend # Initialise once at agent startup — O(n) index build, then O(1) incremental forever db = RawSynrixBackend("~/.agent/state.lattice") # Agent records an attempt — O(1) write node_id = db.add_node( "TASK:stripe_api:attempt", '{"success": false, "error": "timeout", "retry": 3}', parent_id=reasoning_chain_root ) # Agent queries its own history — O(k) where k = matching results, not dataset size # At 100K nodes, this takes 0.07ms. At 10K nodes, same speed. history = db.find_by_prefix("TASK:stripe_api:") # Agent updates a confidence weight in-place — direct mmap write, no transaction overhead node = db.get_node(pattern_node_id) # payload fields (confidence, decay_rate, success_rate) are typed structs # updated directly in mapped memory — no serialize/deserialize cycle
No HTTP. No JSON round-trip. No embedding model warming up. The agent writes, reads, and updates at the speed of memory.
What agents store in Synrix
Synrix nodes are typed. The payload union carries domain-specific structs:
| Node type | What an agent stores |
|---|---|
PATTERN_ |
Learned behaviour patterns with confidence and decay_rate |
TASK_ |
Task attempt records with outcome and parent reasoning chain |
FAILURE_ |
Failure events with error type and retry count |
FUNC_ |
Function call signatures and success rates |
ISA_ |
Ontological relationships between concepts |
AGENT_id: |
Per-agent namespaced state (isolated by namespace prefix) |
The prefix scheme is not a convention. It is enforced by the engine. A node named my_data is rejected. A node named TASK:stripe:attempt_3 is accepted. This is intentional — it prevents prefix explosion and guarantees O(k) query performance.
Reasoning chains
The parent_id field lets an agent record multi-step reasoning as a tree stored in the flat lattice:
TASK:solve_problem (root, id=1001)
├── TASK:solve_problem:step_1 (parent_id=1001, id=1002)
│ └── FAILURE:solve_problem:step_1:attempt_1 (parent_id=1002, id=1003)
└── TASK:solve_problem:step_2 (parent_id=1001, id=1004)
└── TASK:solve_problem:step_2:attempt_1 (parent_id=1004, id=1005)
The hierarchy is stored on disk and survives crashes. Every node is at a fixed offset — O(1) access by ID regardless of depth. You get contextual structure via parent/child relationships with the access speed of a flat array.
The SDK exposes three traversal methods on RawSynrixBackend:
# All direct children of a node — O(k) with prefix_hint children = db.get_children(root_id, prefix_hint="TASK:solve:") # Full subtree as a nested dict — BFS, depth-capped tree = db.get_subtree(root_id, prefix_hint="TASK:solve:") # Ancestor chain from a leaf up to the root — O(1) per hop ancestors = db.get_ancestors(leaf_id)
prefix_hint is optional but recommended — it scopes the candidate scan to the relevant prefix group, keeping traversal O(k) on the subtree size rather than scanning the full dataset.
Latency
These are not synthetic benchmarks. These are measured on real hardware with real data.
| Operation | Latency |
|---|---|
| Node lookup by ID | 192 ns (hot cache) / 3.2 μs (warm) |
| Prefix query — 50K node dataset | 0.31 ms first query / 0.07 ms ongoing |
| Prefix query — 100K node dataset | 0.07 ms (same — O(k), not O(n)) |
| WAL write + fsync | ~1–5 ms (disk-dependent) |
| In-place payload field update | Sub-microsecond (direct mmap write) |
For comparison: Mem0 p95 read is 1.4 seconds. Qdrant p50 is 4 ms. Those systems require an embedding model and a network hop. Synrix requires neither. The comparison is not "which is better" — it is "which was designed for your workload."
Crash safety
An agent process can be killed at any point. Synrix handles this:
- Every write goes to the WAL first (fdatasync)
- The WAL is replayed on next open if the process died mid-write
- Checkpoints happen automatically every N writes
- A corrupt partial write is detected by CRC during load and the WAL takes over
Tested with SIGKILL mid-write at 500 nodes. Zero data loss.
Access paths
Three ways to access Synrix from Python, in order of speed:
| Path | How | Latency | Use when |
|---|---|---|---|
RawSynrixBackend |
ctypes → C directly | Sub-microsecond | Agent on same machine, performance-critical |
SynrixDirectClient |
Shared memory | Microseconds | Agent subprocess, shared state |
SynrixClient |
HTTP to local server | ~1 ms | Multi-process, remote agent, or development |
The HTTP path exists for convenience and multi-agent setups. For a single agent loop, RawSynrixBackend is the right choice.
Install
pip install synrix synrix install-engine # downloads engine binary for your platform synrix run # starts local server (only needed for SynrixClient path)
Free tier: 25,000 nodes, no key required. For unlimited: request a license key.
Platform support
| Platform | Status |
|---|---|
| Windows x86_64 | Ready |
| Linux ARM64 (Jetson, Pi, edge) | Ready |
Edge deployment is a first-class use case. An agent running on a Jetson at the edge needs durable local state that survives power loss. Synrix is 632 KB. It has no runtime dependencies. It runs without a network connection.
What Synrix is not
- Not a vector database. No embeddings. Use Qdrant or ChromaDB if you need fuzzy similarity search.
- Not a general-purpose database. No joins, no arbitrary filters, no DELETE. Use SQLite if you need those.
- Not a human-facing tool. The prefix query model is designed for agents that know their own namespace. If you're building a user-facing search UI, this is the wrong tool.
- Not multi-op transactional. Single-operation atomicity only. If your workload requires "write A and B or neither," you need a different system.
Documentation
- Architecture — Engine internals, why not SQLite
- Benchmarks — Real numbers, honest scale limits
- Durability & crash recovery — WAL, SIGKILL testing
- API — Full SDK reference
- .lattice Format Spec — File format, recovery script
- Privacy & Data Collection — HWID, what's sent, how to verify
License
- Python SDK: MIT
- Engine binary: Proprietary
- Free tier: 25,000 nodes (no key required)
- Unlimited: License key required
Synrix — State infrastructure for agents that operate at machine speed.