1. Introduction
What is NyxKV
NyxKV is a Redis-compatible key-value store built entirely in Nyx. The daemon compiles to a single ~290KB native binary with no external runtime dependencies, and supports 52 commands across strings, lists, sets, hashes, and Pub/Sub.
Architecture
- Protocol: RESP (Redis Serialization Protocol) — works with any Redis client
- Public endpoint:
nyxkv.com:6380, TLS only - Storage: In-memory with binary persistence (
.ndbsnapshots) - Auth: Token-based, with transparent namespace isolation per account
- Persistence: Background snapshot every second; CRC32-verified on load
When to use NyxKV
NyxKV is a good fit when you want a Redis-compatible store without running and maintaining your own Redis server. It works well for:
- Caches and sessions
- Real-time messaging (Pub/Sub)
- Multi-tenant SaaS backends
- Prototyping with the Redis protocol
- Edge deployments where binary size and zero dependencies matter
2. Getting Started
The public NyxKV service requires TLS and a token. Both come from the dashboard.
Prerequisites
- An account at dashboard.nyxkv.com/signup — email and password, no credit card
- A token created in dashboard.nyxkv.com/app/keys
- Any Redis client. The shortest path is
redis-cli
Connect
$ export TOKEN=<paste your token here> $ redis-cli -h nyxkv.com -p 6380 --tls -a $TOKEN PING PONG
If you get Connection reset by peer, you forgot --tls. If you get NOAUTH, you forgot -a $TOKEN.
Your first commands
$ redis-cli -h nyxkv.com -p 6380 --tls -a $TOKEN nyxkv.com:6380> SET hello "world" OK nyxkv.com:6380> GET hello "world"
Understanding responses
NyxKV speaks RESP. Response types you will see:
+OK— simple string (success)-ERR ...— error message(integer) N— integer (counts, booleans)"value"— bulk string (data)(nil)— null (key not found)1) ... 2) ...— array (lists, sets)
3. Connect from the dashboard
The public NyxKV service is multi-tenant: there is no anonymous access. Every connection authenticates with a token issued by the dashboard.
1. Create an account
Go to dashboard.nyxkv.com/signup. Provide an email and a password. There is no credit card and no plan to choose — the free tier is the product.
2. Generate a token
Open dashboard.nyxkv.com/app/keys and click Create token. The token value is shown once; copy it into a password manager or an environment variable. You can create as many tokens as you need and revoke any of them from the same page.
3. Connect
Pass the token as the password to any Redis client, and enable TLS:
$ redis-cli -h nyxkv.com -p 6380 --tls -a $TOKEN nyxkv.com:6380> SET hello "world" OK
Common pitfalls
- Forgot
--tls. The server will close the connection. Some clients reportConnection reset by peer. - Forgot the token. The first command after connect returns
-NOAUTH Authentication required. - Wrong port. The service only listens on 6380. There is no plain-text port.
- Token revoked. Revoking a token from the dashboard takes effect on the next request. Existing connections are not killed proactively.
./nyxkv (no --public, no --tls), the docs in this book apply too — just drop --tls -a $TOKEN from the examples and the prompt becomes 127.0.0.1:6380>.4. Strings
Strings are the simplest data type. Every key starts as a string unless you use list, set, or hash commands.
SET key value
Stores a string value. Overwrites any existing value and clears any TTL.
> SET user:1 "Alice" OK
GET key
Retrieves the string value. Returns (nil) if the key doesn't exist.
> GET user:1 "Alice" > GET nonexistent (nil)
SETNX key value
Sets the value only if the key does not already exist. Returns 1 if set, 0 if skipped.
> SETNX lock:job42 "worker-1" (integer) 1 > SETNX lock:job42 "worker-2" (integer) 0 # worker-1 holds the lock
SETEX key seconds value
Sets a value with an expiration time (in seconds), atomically.
> SETEX session:abc 3600 "user:1" OK # Expires in 1 hour
GETSET key value
Sets a new value and returns the old value atomically.
> SET counter 10 OK > GETSET counter 0 "10" # Returned old value, counter is now 0
INCR key
Increments an integer value by 1. Creates the key with value 0 if it doesn't exist.
> SET visitors 0 OK > INCR visitors (integer) 1 > INCR visitors (integer) 2
DECR key
Decrements an integer value by 1. Creates the key with value 0 if it doesn't exist.
> SET stock 50 OK > DECR stock (integer) 49
APPEND key value
Appends the value to the end of the string. Creates the key if it doesn't exist. Returns the new length.
> SET greeting "Hello" OK > APPEND greeting " World" (integer) 11 > GET greeting "Hello World"
STRLEN key
Returns the length of the string value in bytes. Returns 0 if the key doesn't exist.
> STRLEN greeting (integer) 11
MSET key value [key value ...]
Sets multiple key-value pairs in a single command.
> MSET name "Alice" age "30" city "NYC" OK
MGET key [key ...]
Gets multiple values in a single command. Returns (nil) for missing keys.
> MGET name age city missing 1) "Alice" 2) "30" 3) "NYC" 4) (nil)
Pattern: Atomic counter
# Page view counter — no race conditions > INCR page:/docs/intro (integer) 1 > INCR page:/docs/intro (integer) 2 > GET page:/docs/intro "2"
5. Lists
Lists are ordered sequences of strings. They support push/pop from both ends, making them ideal for queues and stacks.
LPUSH key value [value ...]
Pushes one or more values to the head (left) of the list. Creates the list if it doesn't exist.
> LPUSH tasks "Deploy v2" (integer) 1 > LPUSH tasks "Fix bug #42" (integer) 2
RPUSH key value [value ...]
Pushes one or more values to the tail (right) of the list.
> RPUSH logs "2024-01-01 started" (integer) 1 > RPUSH logs "2024-01-01 completed" (integer) 2
LPOP key
Removes and returns the first element (head).
> LPOP tasks "Fix bug #42"
RPOP key
Removes and returns the last element (tail).
> RPOP tasks "Deploy v2"
LLEN key
Returns the number of elements in the list.
> LPUSH items "a" "b" "c" (integer) 3 > LLEN items (integer) 3
LRANGE key start stop
Returns elements from index start to stop (inclusive). Supports negative indices: -1 is the last element.
> RPUSH colors "red" "green" "blue" "yellow" (integer) 4 > LRANGE colors 0 -1 1) "red" 2) "green" 3) "blue" 4) "yellow" > LRANGE colors 1 2 1) "green" 2) "blue"
LINDEX key index
Returns the element at the given index. Supports negative indices.
> LINDEX colors 0 "red" > LINDEX colors -1 "yellow"
Pattern: Job queue
# Producer: push jobs to the left > LPUSH jobs '{"type":"email","to":"alice@example.com"}' (integer) 1 # Consumer: pop jobs from the right (FIFO) > RPOP jobs "{\"type\":\"email\",\"to\":\"alice@example.com\"}"
6. Sets
Sets are unordered collections of unique strings. Duplicates are automatically ignored.
SADD key member [member ...]
Adds one or more members. Returns the number of new members added (ignores duplicates).
> SADD tags "nyx" "fast" "tiny" (integer) 3 > SADD tags "nyx" (integer) 0 # "nyx" was already in the set
SREM key member [member ...]
Removes one or more members. Returns the number of members removed.
> SREM tags "tiny" (integer) 1
SISMEMBER key member
Tests if a member exists in the set. Returns 1 if yes, 0 if no.
> SISMEMBER tags "nyx" (integer) 1 > SISMEMBER tags "slow" (integer) 0
SMEMBERS key
Returns all members of the set.
> SMEMBERS tags 1) "nyx" 2) "fast"
SCARD key
Returns the number of members (cardinality).
> SCARD tags (integer) 2
Pattern: Unique visitors
# Track unique visitors per page > SADD visitors:/home "user:1" "user:2" "user:1" (integer) 2 > SCARD visitors:/home (integer) 2 # user:1 counted only once
7. Hashes
Hashes store field-value pairs under a single key. Think of them as objects or dictionaries.
HSET key field value [field value ...]
Sets one or more field-value pairs. Returns the number of new fields added.
> HSET user:1 name "Alice" email "alice@example.com" age "30" (integer) 3
HGET key field
Gets the value of a single field.
> HGET user:1 name "Alice" > HGET user:1 phone (nil)
HDEL key field [field ...]
Deletes one or more fields. Returns the number deleted.
> HDEL user:1 age (integer) 1
HGETALL key
Returns all fields and values as an interleaved array.
> HGETALL user:1 1) "name" 2) "Alice" 3) "email" 4) "alice@example.com"
HKEYS key
Returns all field names.
> HKEYS user:1 1) "name" 2) "email"
HVALS key
Returns all values.
> HVALS user:1 1) "Alice" 2) "alice@example.com"
HEXISTS key field
Tests if a field exists. Returns 1 or 0.
> HEXISTS user:1 email (integer) 1
HLEN key
Returns the number of fields.
> HLEN user:1 (integer) 2
Pattern: User profile
# Store structured data as a hash > HSET product:42 name "Widget" price "19.99" stock "150" (integer) 3 # Read a single field > HGET product:42 price "19.99" # Update one field without touching others > HSET product:42 stock "149" (integer) 0
8. Key Management
DEL key [key ...]
Deletes one or more keys of any type. Returns the number of keys deleted.
> SET temp "data" OK > DEL temp (integer) 1 > DEL temp (integer) 0
EXISTS key
Checks if a key exists (any type). Returns 1 or 0.
> EXISTS user:1 (integer) 1 > EXISTS nonexistent (integer) 0
TYPE key
Returns the type of the value stored at key: string, list, set, hash, or none.
> SET name "Alice" OK > LPUSH items "a" (integer) 1 > TYPE name string > TYPE items list
KEYS pattern
Lists all keys matching the pattern. Use * for all keys.
> KEYS * 1) "user:1" 2) "name" 3) "items" > KEYS user:* 1) "user:1"
DBSIZE
Returns the number of keys in your namespace.
> DBSIZE (integer) 3
9. Expiration & TTL
Any key can have an expiration time. Once expired, the key is automatically deleted on next access (lazy deletion).
EXPIRE key seconds
Sets a timeout on a key. After seconds elapse, the key is deleted. Returns 1 if set, 0 if key doesn't exist.
> SET session "active" OK > EXPIRE session 3600 (integer) 1 # Session expires in 1 hour
TTL key
Returns the remaining time to live in seconds.
-2— key does not exist-1— key exists but has no expirationN— seconds remaining
> TTL session (integer) 3598 > TTL user:1 (integer) -1 > TTL nonexistent (integer) -2
PERSIST key
Removes the expiration from a key. Returns 1 if a TTL was removed, 0 otherwise.
> PERSIST session (integer) 1 > TTL session (integer) -1
Pattern: Session with auto-expiry
# Create a session that expires in 30 minutes > SETEX sess:abc123 1800 '{"user":"alice","role":"admin"}' OK # Check remaining time > TTL sess:abc123 (integer) 1798 # Extend on activity > EXPIRE sess:abc123 1800 (integer) 1
10. Pub/Sub
Pub/Sub enables real-time messaging between clients. Publishers send messages to channels; all subscribers on that channel receive them instantly.
SUBSCRIBE channel
Subscribes to a channel. The connection enters subscriber mode and blocks, receiving messages as they arrive.
# Terminal 1: subscriber > SUBSCRIBE notifications 1) "subscribe" 2) "notifications" 3) (integer) 1 # Waiting for messages...
PUBLISH channel message
Sends a message to all subscribers of the channel. Returns the number of subscribers who received it.
# Terminal 2: publisher > PUBLISH notifications "New order #1234" (integer) 1
The subscriber in Terminal 1 receives:
1) "message"
2) "notifications"
3) "New order #1234"UNSUBSCRIBE channel
Unsubscribes from a channel and exits subscriber mode.
./nyxkv daemon during development — the public TLS endpoint accepts all other commands.Pattern: Real-time notifications
# Chat room > SUBSCRIBE chat:general # Another client: > PUBLISH chat:general "alice: Hello everyone!" # All subscribers in chat:general receive the message
11. Persistence
NyxKV writes data to disk as binary snapshots in a .ndb file with a CRC32 checksum.
How it works
- Background snapshot: a dedicated thread writes the snapshot to
dump.ndbonce per second. Client requests do not block on persistence. - Recovery: on startup, if
dump.ndbexists, the daemon validates its CRC32 and loads everything. If the checksum fails, it refuses to load (rather than silently starting empty). - Loss window: a hard kill (
SIGKILL, power loss) can lose at most one second of writes. A graceful restart loses nothing in steady state.
SIGTERM handler that used to flush on shutdown is intentionally disabled in the current build — it ran inside an async-signal-unsafe context and corrupted snapshots. The bg-saver covers normal restarts; the worst case (kill -9) loses ≤1 s.SAVE
Triggers a synchronous snapshot to dump.ndb. Blocks until complete.
> SAVE OK
BGSAVE
Triggers a background snapshot. Returns immediately.
> BGSAVE Background saving started
LASTSAVE
Returns the Unix timestamp of the last successful save.
> LASTSAVE (integer) 1711987200
The .ndb format
Binary snapshot format with a header, entries, and a CRC32 checksum:
- Header:
NYXDBmagic + version byte - Metadata: timestamp + key count
- Entries: type (string / list / set / hash / token) + TTL + key + value
- Footer: CRC32 checksum + EOF marker
If the CRC32 doesn’t match on load, NyxKV reports corruption and refuses to start, instead of silently losing data.
12. Authentication & Multi-tenant
The public NyxKV service is multi-tenant. Each authenticated account has its own namespace, and tokens are issued and revoked from the dashboard, not the protocol.
Connecting with a token
AUTH token
Authenticates the connection. All subsequent commands operate in your account’s namespace. Most clients call this for you when you pass a password.
> AUTH a1b2c3d4e5f6... OK > SET mykey "mydata" OK # Internally stored as: alice::mykey
WHOAMI
Returns the current authentication status.
> WHOAMI "alice:free"
How namespace isolation works
When authenticated, all keys are transparently prefixed with your account’s id. This means:
- Account “alice” runs
SET mykey "data"→ stored asalice::mykey - Account “bob” runs
SET mykey "other"→ stored asbob::mykey - Neither account can see or access the other’s keys
- KEYS / DBSIZE / TYPE / EXISTS scope to your namespace, without the prefix
Tokens are managed in the dashboard
To create, list, or revoke tokens, sign in to dashboard.nyxkv.com/app/keys:
- Create token — the value is shown once. Save it. You can create as many as you want.
- Revoke token — takes effect on the next request from the affected client. Existing connections are not killed proactively.
- List tokens — you see token id, creation date, and last-seen activity. Token values are never displayed again.
There is no TOKEN_CREATE/TOKEN_REVOKE/TOKEN_LIST command on the public service. Those exist on the daemon but are gated to admin connections only, so the dashboard is the single source of truth for who has access.
Pattern: per-environment tokens
Issue separate tokens for staging and production. If a token leaks, revoke just that one without affecting the other environment:
- Token labeled
app-staging— used by the staging deployment - Token labeled
app-production— used by the production deployment - Token labeled
local-dev— used by your laptop
13. Client Libraries
NyxKV speaks RESP, so any Redis client library works. The public service requires TLS and a token — the snippets below show the minimum viable connect for popular languages.
Python (redis-py)
import os, redis r = redis.Redis( host="nyxkv.com", port=6380, ssl=True, password=os.environ["NYXKV_TOKEN"], ) r.set("key", "value") val = r.get("key") # b"value" r.incr("counter") r.lpush("queue", "job1") r.hset("user:1", mapping={"name": "Alice", "age": "30"})
Node.js (ioredis)
import Redis from "ioredis"; const r = new Redis({ host: "nyxkv.com", port: 6380, tls: {}, // enable TLS password: process.env.NYXKV_TOKEN, }); await r.set("key", "value"); const val = await r.get("key"); // "value" await r.lpush("queue", "job1"); await r.hset("user:1", "name", "Alice");
Go (go-redis)
import ( "crypto/tls" "os" "github.com/redis/go-redis/v9" ) rdb := redis.NewClient(&redis.Options{ Addr: "nyxkv.com:6380", Password: os.Getenv("NYXKV_TOKEN"), TLSConfig: &tls.Config{}, }) rdb.Set(ctx, "key", "value", 0) val, _ := rdb.Get(ctx, "key").Result() rdb.LPush(ctx, "queue", "job1") rdb.HSet(ctx, "user:1", "name", "Alice")
Rust (redis-rs)
let token = std::env::var("NYXKV_TOKEN")?; let url = format!("rediss://:{}@nyxkv.com:6380", token); let client = redis::Client::open(url)?; let mut con = client.get_connection()?; redis::cmd("SET").arg("key").arg("value").execute(&mut con); let val: String = redis::cmd("GET").arg("key").query(&mut con)?;
./nyxkv on your laptop without --public and --tls, drop the TLS config and the password from these snippets and point at localhost:6380.14. Patterns & Recipes
Session store
Use SETEX to store sessions with automatic expiration.
# Create session (30 min TTL) > SETEX sess:abc123 1800 '{"user":"alice","role":"admin"}' OK # Read session > GET sess:abc123 "{\"user\":\"alice\",\"role\":\"admin\"}" # Extend on activity > EXPIRE sess:abc123 1800 (integer) 1 # Logout > DEL sess:abc123 (integer) 1
Rate limiter
Use INCR with EXPIRE to implement a per-key rate limiter.
# Track requests per user per minute > INCR rate:user:42 (integer) 1 > EXPIRE rate:user:42 60 (integer) 1 # On each request: INCR, check if > threshold, reject if so > GET rate:user:42 "47"
Cache with TTL
Cache expensive computations with automatic invalidation.
# Cache API response for 5 minutes > SETEX cache:api:/users 300 '[{"id":1,"name":"Alice"}]' OK # Check cache before calling API > GET cache:api:/users "[{\"id\":1,\"name\":\"Alice\"}]" # Cache miss → call API → SETEX again
Job queue (FIFO)
Use LPUSH + RPOP for a first-in-first-out queue.
# Enqueue jobs > LPUSH jobs '{"task":"send_email","to":"alice@example.com"}' (integer) 1 > LPUSH jobs '{"task":"resize_image","id":42}' (integer) 2 # Dequeue (oldest first) > RPOP jobs "{\"task\":\"send_email\",\"to\":\"alice@example.com\"}" # Check queue length > LLEN jobs (integer) 1
Configuration store
Use hashes to store application configuration with per-field updates.
> HSET config:app max_retries "3" timeout "30" debug "false" (integer) 3 # Update one setting without touching others > HSET config:app debug "true" (integer) 0 # Read all config > HGETALL config:app 1) "max_retries" 2) "3" 3) "timeout" 4) "30" 5) "debug" 6) "true"
Leaderboard
Use INCR for simple score tracking.
> INCR score:alice (integer) 1 > INCR score:alice (integer) 2 > INCR score:bob (integer) 1 > MGET score:alice score:bob 1) "2" 2) "1"
Feature flags
Use sets to manage feature flags per user group.
> SADD feature:dark-mode "user:1" "user:3" "user:7" (integer) 3 # Check if user has the feature > SISMEMBER feature:dark-mode "user:3" (integer) 1 > SISMEMBER feature:dark-mode "user:5" (integer) 0
15. Limits
There is one tier on NyxKV: free. There is no Pro plan. There is no Enterprise plan. The free tier is the product.
What you get
| Limit | Value |
|---|---|
| Requests per second | 1000 |
| Maximum keys | 10,000 |
| Maximum value size | 1 MB |
| TTL on every key | 24 hours |
| TLS | required |
| Multi-tenant isolation | included |
| Pub/Sub | included (over plain TCP, see chapter 10) |
Free tier details
- Rate limit: 1000 requests per second per account, enforced as a 1-second sliding window.
- Key limit: 10,000 keys per account. Once reached, new SET / LPUSH / SADD / HSET return an error until you delete or let keys expire.
- Value size: 1 MB per value (1,048,576 bytes). Larger writes are rejected.
- TTL cap: every key has a 24-hour TTL (86,400 seconds). EXPIRE values larger than that are clamped down.
- KEYS: returns at most 100 keys, scoped to your namespace.
- Disabled commands on the public service: FLUSHDB, FLUSHALL, CONFIG, SAVE, BGSAVE.
How rate limiting works
Per-account, sliding-window: every connection counts against your account’s budget. When the limit is exceeded, the server responds with:
-ERR rate limit exceededPricing
$0. Forever. No credit card. No upgrade prompts. If you outgrow what the free tier offers, you have outgrown what this service is for — run the open-source daemon on your own host.
Server commands reference
PING [message]
Returns PONG, or echoes the message. Used for health checks and latency testing.
INFO
Returns server statistics in Redis INFO format: version, total commands processed, keyspace info.
CONFIG GET save
Stub for redis-benchmark compatibility. Disabled on the public service.
FLUSHDB / FLUSHALL
Admin-only on the daemon. Not available on the public service.
16. Agent Memory (beta)
What it is
Agent Memory is private, persistent memory for AI agents and the apps built around them — on top of plain NyxKV, not a separate service. It solves two things a coding agent does not give you on its own: memory that survives after a session ends, and memory that follows you across machines.
Under the hood it is just keys. Entries live at mem:<collection>:<key> inside your normal token namespace, isolated the same way every other key is (see chapter 12). There is no separate account, no separate token, no separate protocol — anything that speaks RESP can read and write agent memory.
Quickstart
Four steps: token, SDK, a round-trip, then the Claude Code plugin.
1. Token
Same token as everywhere else in NyxKV. Create one at dashboard.nyxkv.com/app/keys and save it where the SDK looks for it by default:
$ umask 077 && printf '%s' 'YOUR_TOKEN' > ~/.nyxkv-token
2. Build the SDK
nyx-kv-sdk is a small Nyx CLI that wraps the collection/key model below. Clone nyx-kv and build it with the Nyx toolchain:
$ cd sdk && nyx build $ ./nyx-kv-sdk help
3. Put and get an entry
$ ./nyx-kv-sdk put notes readme "first entry" OK $ ./nyx-kv-sdk get notes readme first entry
4. Install the Claude Code plugin
sdk/claude/ is a drop-in plugin with two core skills, memory-pull and memory-push (plus optional friction-share / friction-review for leaving notes between agent instances). Copy it wherever Claude Code loads your plugins from, then just ask in plain language: “pull my memory” at the start of a session, “save my memory” before you close it. Both call the SDK underneath.
5. Sync across machines
The plugin wraps exactly this — run it directly if you want to sync a directory of markdown files without an agent in the loop:
# machine A $ ./nyx-kv-sdk sync-push myproject ./notes sync-push: 3 archivo(s) de ./notes → colección myproject # machine B, same token $ ./nyx-kv-sdk sync-pull myproject ./notes sync-pull: 3 archivo(s) de colección myproject → ./notes
The mem:* key model
Everything lives under the mem: prefix, which keeps it visually distinct from cache and session keys and lets the dashboard enumerate it without scanning your whole namespace:
mem:<collection>:<key>— one entry (a string value)mem:<collection>:__keys__— a Set index of the keys in that collection, used bylist/sync-pull/the dashboard instead ofKEYS(which caps at 100 results on this tier — see chapter 15)mem:__collections__— a Set index of every collection name in your namespace, so the dashboard can list them all without a full scan
A “collection” is just a namespace inside your token's keyspace — a project, a topic, whatever grouping is useful to you. The Claude Code plugin defaults to one collection per project (agentmem-<project directory name>), overridable with the NYXKV_MEMORY_COLLECTION env var.
Entry values are base64-encoded on the wire with a b64: prefix, so multiline markdown survives RESP intact. The SDK and the plugin encode/decode this transparently — if you inspect an entry directly with redis-cli GET, you will see the raw b64:... form, not plain text.
Viewing your memory
The dashboard has a built-in viewer at dashboard.nyxkv.com/app/memory. It lists every collection and key under your token's mem:* namespace, lets you open an entry to read the decoded content, and delete individual entries. It reuses your dashboard session — no separate memory-specific login.
Limits (beta)
Agent Memory is beta. The honest constraints, so there are no surprises:
- No dedicated quota yet.
mem:*entries count against the same free-tier caps as any other key: 10,000 keys and 1 MB per value, per account (chapter 15). - No forced TTL.
mem:*keys are exempt from the public service's standard 24h TTL — agent memory does not expire on its own. It still counts against your account's key/byte quota (previous bullet), and an explicitEXPIREyou set yourself is still honored. - One collection per project by default. The plugin does not fan a project's memory across multiple collections automatically; set
NYXKV_MEMORY_COLLECTIONif you want to split it up yourself. - No semantic search. This is a key-value store, not a vector database. Agent Memory gives you durable storage and retrieval by exact key, not similarity search over content.
- Shared public host. TLS in transit and per-token isolation, but no client-side encryption. Do not put secrets in agent memory — the plugin's default excludes (
user_*.md,*secret*,*private*,*.local.md) are a safety net, not a guarantee. Review them for your own setup.