26 Unix tools. One binary. Zero dependencies.·the missing coreutils for the agent era·vrk mcp - expose all 26 tools to any AI agent·curl vrk.sh/install.sh | sh - ready in 5 seconds·

vrk kv

vrk kv is a persistent key-value store backed by SQLite with namespaces, TTL, and atomic counters.

The problem

A pipeline needs to remember state between runs: a cursor position, a processed count, a last-run timestamp. Writing to a flat file works until two cron jobs overlap. Both read the same cursor. Both process the same 200 items. Redis solves concurrency but is a whole server for one key.

The solution

vrk kv is a persistent key-value store backed by SQLite. Namespaces isolate different pipelines. TTL handles automatic expiry. Atomic incr/decr is safe under concurrent access. The database lives at ~/.vrk.db (override with VRK_KV_PATH). No server, no config.

Before and after

Before

echo "12345" > /tmp/last_cursor.txt
CURSOR=$(cat /tmp/last_cursor.txt)
# no atomic increment, no TTL, no namespaces
# concurrent writes corrupt the file

After

vrk kv set cursor "12345" --ttl 24h
vrk kv get cursor
vrk kv incr --ns pipeline processed

Example

vrk kv set --ns nightly-pipeline last_run "$(vrk epoch --now)" --ttl 24h

Exit codes

CodeMeaning
0Success
1Key not found, not a number, or database error
2Usage error - unknown subcommand, missing args

Flags

FlagShortTypeDescription
--nsstringNamespace (default “default”)
--quiet-qboolSuppress stderr output
--ttldurationExpiry duration (set only); 0 = no expiry
--dry-runboolPrint intent without writing (set only)
--json-jboolEmit errors as JSON (get, incr, decr)
--byintDelta for incr/decr (must be >= 1)

Subcommands

set - store a value

$ vrk kv set mykey "hello world"
$ vrk kv set --ns cache api_response "..." --ttl 1h

Values can also come from stdin when the value argument is omitted:

echo "pipeline output" | vrk kv set --ns results latest

get - retrieve a value

$ vrk kv get mykey
hello world

Exits 1 if the key doesn’t exist or has expired:

$ vrk kv get nonexistent
error: kv get: key not found
$ echo $?
1

del - delete a key

vrk kv del mykey

Silent if the key doesn’t exist. Always exits 0.

list - show all keys

$ vrk kv list --ns cache
api_response
last_fetch
status

Lists all keys in the namespace, sorted alphabetically. Expired keys are not shown.

incr / decr - atomic counters

$ vrk kv incr counter
1
$ vrk kv incr counter
2
$ vrk kv incr counter --by 10
12
$ vrk kv decr counter
11

Missing keys start at 0. Increments are atomic - safe for concurrent access from multiple processes.

Flag details

–ns (namespace)

Isolates keys between different pipelines or tasks. Defaults to “default”.

vrk kv set --ns daily-summary status "running"
vrk kv set --ns weekly-report status "pending"
vrk kv get --ns daily-summary status    # "running"
vrk kv get --ns weekly-report status    # "pending"

–ttl (time to live)

Automatically expires keys after a duration. Supports Go duration format: 1s, 5m, 24h, 168h (7 days).

vrk kv set --ns cache response "$DATA" --ttl 1h
# After 1 hour:
vrk kv get --ns cache response
# error: kv get: key not found

–dry-run

Shows what set would do without writing:

$ vrk kv set --ns prod important_key "value" --dry-run
kv: would set important_key = "value" (ns: prod, ttl: 0s)

A complete agent loop

#!/bin/bash
# Morning: mark pipeline started
vrk kv set --ns daily-summary run_at "$(vrk epoch --now)"
vrk kv set --ns daily-summary status "running"

# Process items, tracking count
for item in $(cat queue.txt); do
  process "$item"
  vrk kv incr --ns daily-summary processed
done

# Evening: check what happened
echo "Status: $(vrk kv get --ns daily-summary status)"
echo "Processed: $(vrk kv get --ns daily-summary processed)"

# Store the final result with a 7-day TTL
vrk kv set --ns daily-summary last_result "$OUTPUT" --ttl 168h
vrk kv set --ns daily-summary status "complete"

Pipeline integration

Track progress in a batch pipeline

# Process URLs, track successes and failures in kv
vrk kv set --ns batch run_id "$(vrk moniker --seed $RANDOM)"
for url in $(cat urls.txt); do
  if vrk grab "$url" | vrk prompt --system 'Summarize' > /dev/null 2>&1; then
    vrk kv incr --ns batch success
  else
    vrk kv incr --ns batch failure
  fi
done
echo "Done: $(vrk kv get --ns batch success) ok, $(vrk kv get --ns batch failure) failed"

Cache LLM responses to avoid re-processing

# Check cache before calling the LLM
KEY=$(echo "$INPUT" | vrk digest --bare)
CACHED=$(vrk kv get --ns llm-cache "$KEY" 2>/dev/null)
if [ -n "$CACHED" ]; then
  echo "$CACHED"
else
  RESULT=$(echo "$INPUT" | vrk prompt --system 'Analyze this')
  vrk kv set --ns llm-cache "$KEY" "$RESULT" --ttl 24h
  echo "$RESULT"
fi

Resume a pipeline from where it left off

# Store cursor position; resume after crash
CURSOR=$(vrk kv get --ns ingest cursor 2>/dev/null || echo "0")
cat data.jsonl | tail -n +$((CURSOR + 1)) | \
  while IFS= read -r line; do
    process "$line"
    CURSOR=$((CURSOR + 1))
    vrk kv set --ns ingest cursor "$CURSOR"
  done

When it fails

Key not found:

$ vrk kv get nonexistent
error: kv get: key not found
$ echo $?
1

Missing subcommand:

$ vrk kv
usage error: kv: subcommand required (set, get, del, list, incr, decr)
$ echo $?
2

Increment on a non-numeric value:

$ vrk kv set mykey "not a number"
$ vrk kv incr mykey
error: kv incr: value is not an integer
$ echo $?
1