Step-by-step instructions for setting up primkit primitives with Cloudflare R2 replication.
- Go 1.26+ installed
- A Cloudflare account (free tier works)
git clone [email protected]:propifly/primkit.git
cd primkit
make buildBinaries are placed in ./bin/:
bin/taskprim
bin/stateprim
bin/knowledgeprim
bin/queueprim
# taskprim — add and list tasks
./bin/taskprim add "Review PR from Johanna" --list sprint-12
./bin/taskprim list
# stateprim — set and get state
./bin/stateprim set agents/johanna last_run '"2025-03-03T10:00:00Z"'
./bin/stateprim get agents/johanna last_runBoth create SQLite databases at ~/.taskprim/default.db and ~/.stateprim/default.db respectively.
# queueprim — enqueue and dequeue jobs
./bin/queueprim enqueue demo '{"task":"hello"}'
./bin/queueprim dequeue demo --worker localCreates ~/.queueprim/default.db on first use.
R2 is S3-compatible object storage from Cloudflare. Free tier includes 10 GB storage and 10 million reads/month.
- Log in to Cloudflare Dashboard
- In the left sidebar: Storage & databases → R2 object storage → Overview
- Click Create bucket
- Name it (e.g.,
primkit-replication) - Location: Automatic is fine
- Click Create bucket
- Go back to the R2 overview page (click "R2 object storage" → "Overview" in the left sidebar — not inside a bucket)
- In the top-right area of the overview page, click Manage R2 API Tokens
- Click Create API token
- Token name:
primkit-replication - Permissions: Object Read & Write
- Specify bucket (optional): scope to your bucket for security
- Click Create API Token
- Copy and save the Access Key ID and Secret Access Key — they are shown only once
Your Account ID is in the dashboard URL:
https://dash.cloudflare.com/<ACCOUNT_ID>/r2/overview
Or: go to any domain → Overview → right sidebar shows Account ID.
Create a config file (e.g., config.yaml):
storage:
db: ~/.taskprim/default.db
replicate:
enabled: true
provider: r2
bucket: primkit-replication
path: taskprim.db
endpoint: https://<YOUR_ACCOUNT_ID>.r2.cloudflarestorage.com
access_key_id: ${R2_ACCESS_KEY_ID}
secret_access_key: ${R2_SECRET_ACCESS_KEY}
server:
port: 8090Set the credentials as environment variables:
export R2_ACCESS_KEY_ID="your-access-key-id-here"
export R2_SECRET_ACCESS_KEY="your-secret-access-key-here"Tip: Add these to a
.envfile and source it, or use a secrets manager. Never commit credentials to git.
# Every CLI command now replicates when config has replication enabled
./bin/taskprim add "Test replication" --list demo --config config.yaml
./bin/taskprim add "Another task" --list demo --config config.yaml
./bin/taskprim list --config config.yamlEach command: restores if DB missing → opens DB → starts Litestream → runs command → syncs → stops Litestream.
Check your R2 bucket in the Cloudflare dashboard. You should see files under the taskprim.db/ prefix (Litestream creates LTX files and snapshots).
# Delete the local database
rm -rf ~/.taskprim/
# Restore from R2
./bin/taskprim restore --config config.yaml
# Verify your tasks are back
./bin/taskprim list --config config.yaml# Start the HTTP API — Litestream runs continuously in the background
./bin/taskprim serve --config config.yaml
# In another terminal, add tasks via API
curl -X POST http://localhost:8090/v1/tasks \
-H "Content-Type: application/json" \
-d '{"what":"Created via API","list":"demo","source":"curl"}'
# Stop the server with Ctrl+C — final sync happens automaticallySame setup, different DB path and port:
storage:
db: ~/.stateprim/default.db
replicate:
enabled: true
provider: r2
bucket: primkit-replication
path: stateprim.db
endpoint: https://<YOUR_ACCOUNT_ID>.r2.cloudflarestorage.com
access_key_id: ${R2_ACCESS_KEY_ID}
secret_access_key: ${R2_SECRET_ACCESS_KEY}
server:
port: 8091./bin/stateprim set agents/johanna last_run '"2025-03-03"' --config stateprim-config.yaml
./bin/stateprim restore --config stateprim-config.yamlEvery config field can be overridden with env vars (prefixes: TASKPRIM_, STATEPRIM_, KNOWLEDGEPRIM_, QUEUEPRIM_):
| Env Var | Config Field |
|---|---|
TASKPRIM_DB |
storage.db |
TASKPRIM_REPLICATE_ENABLED |
storage.replicate.enabled |
TASKPRIM_REPLICATE_PROVIDER |
storage.replicate.provider |
TASKPRIM_REPLICATE_BUCKET |
storage.replicate.bucket |
TASKPRIM_REPLICATE_PATH |
storage.replicate.path |
TASKPRIM_REPLICATE_ENDPOINT |
storage.replicate.endpoint |
TASKPRIM_REPLICATE_ACCESS_KEY_ID |
storage.replicate.access_key_id |
TASKPRIM_REPLICATE_SECRET_ACCESS_KEY |
storage.replicate.secret_access_key |
TASKPRIM_SERVER_PORT |
server.port |
This means you can skip the config file entirely:
export TASKPRIM_REPLICATE_ENABLED=true
export TASKPRIM_REPLICATE_PROVIDER=r2
export TASKPRIM_REPLICATE_BUCKET=primkit-replication
export TASKPRIM_REPLICATE_PATH=taskprim.db
export TASKPRIM_REPLICATE_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
export TASKPRIM_REPLICATE_ACCESS_KEY_ID=your-key
export TASKPRIM_REPLICATE_SECRET_ACCESS_KEY=your-secret
# No --config needed — env vars are enough
./bin/taskprim add "Works without config file" --list demoFor Claude Desktop or other MCP clients:
{
"mcpServers": {
"taskprim": {
"command": "/path/to/taskprim",
"args": ["mcp", "--transport", "stdio", "--config", "/path/to/config.yaml"]
}
}
}Replication runs automatically during the MCP session. When the client disconnects, a final sync pushes remaining changes to R2.
"replication is not enabled in the config"
→ Check that storage.replicate.enabled: true is set in your config, or TASKPRIM_REPLICATE_ENABLED=true is exported.
Restore says "no generation found, creating new database" → No backup exists on R2 yet. This is normal on first run — Litestream needs to replicate at least once before restore works.
"restoring from replica" errors with access denied → Check your R2 credentials and that the bucket name matches exactly. Verify the endpoint URL includes your Cloudflare Account ID.
Writes seem slow with replication enabled → Litestream monitors the WAL file asynchronously. Writes to SQLite are not slowed down — replication happens in the background. The only added latency is the final sync on command exit.