Build

Agent instructions

How AI coding agents install Aeron Cache, use the embedded clients, call the HTTP, WebSocket and SSE APIs directly, and generate clients for new languages.

Who this page is for

This page is written for AI coding agents (and the humans directing them). Paste it, or the link to it, into your agent’s context and it has what it needs to install Aeron Cache, connect to it, and use every interface — without guessing.

The rules, in priority order:

  1. Prefer the embedded client libraries (Java, TypeScript, Python, Rust). They already handle correlation, reconnection, the local mirror, and operationStatus mapping. See embedded clients.
  2. Fall back to the raw APIs (HTTP, WebSocket, SSE, bidirectional WebSocket) when there is no library for the language, when you are scripting, or when you are debugging.
  3. Generate a new client only when the target language has no library — see Generating a client for a new language.
  4. The OpenAPI specs are the source of truth. If this page and a spec disagree, the spec wins; say so to the user.

Copy-paste prompt. “Read https://aeron-cache.pages.dev/agent-instructions (or the Markdown of this page). Install Aeron Cache locally, verify it with curl, then use the embedded client library for my language. Only call the raw API if no library exists.”

Mental model (read before writing code)

  • Aeron Cache is a key-value and counter store. Values are strings. To store structured data, store a JSON document as a string — that is also what makes it patchable.
  • Caches are created explicitly (DYNAMIC_CACHE_CREATION defaults to false). A put to a cache that does not exist returns UNKNOWN_CACHE. Create first.
  • Regular caches and counter caches are separate flavours with separate routes (/api/v1/cache/... vs /api/v1/counters/...).
  • Business outcomes live in operationStatus, not only in HTTP codes: SUCCESS, CACHE_EXISTS, UNKNOWN_CACHE, UNKNOWN_KEY, ERROR, … Always read the field. CACHE_EXISTS on create is normal control flow, not a failure.
  • Writes go to the server; reads can be local. The embedded clients keep a mirror updated by streaming events (ADD_ITEM, PATCH_ITEM, REMOVE_ITEM, CLEAR_CACHE, DELETE_CACHE). Local reads are only as fresh as the stream — wait for hydration or a subscribed ack before asserting on them.
  • Do not invent numbers. No latency or throughput benchmarks are published yet. Do not quote any; if the user needs figures, tell them to measure their own workload.

Step 1 — Install and run the backend

Prerequisites: macOS or Linux with Homebrew; ports 3000 (UI), 7070 (HTTP) and 7071 (WebSocket) free. SSE uses 7072.

brew tap bhf/aeron-cache
brew install aeron-cache
aeron-cache            # runs the single-process monolith + UI; leave it running

Verify: the process prints Aeron Cache is running!, and:

curl -s http://localhost:7070/api/v1/caches   # -> a JSON array (empty on first run)

Run aeron-cache in the background or a second terminal — it blocks until Ctrl+C. Config lives in ~/.aeron-cache/backend.env; log in ~/.aeron-cache/aeron-cache.log. Restart after editing config.

Setting (backend.env)DefaultEffect
WEBSOCKET_GATEWAY_ENABLEDtrueWebSocket routes on :7071 (including bidi)
SSE_GATEWAY_ENABLEDtrueSSE routes on :7072
AERON_GATEWAY_ENABLEDfalseNative Aeron SBE gateway (Java and Rust clients only)
GATEWAY_TRANSPORT_MEDIAudpudp or ipc for the Aeron gateway
DYNAMIC_CACHE_CREATIONfalseAuto-create caches on first write

Optional: install the CLI (brew tap bhf/aeron-cache-cli && brew install aeron-cache-cli) for quick manual checks — aeron-cache create <cache>, aeron-cache insert <cache> <key> <value>, aeron-cache get <cache> <key>.

Step 2 — Choose an interface

You needUse
A library exists for the language and you want local readsEmbedded client (HTTP + WebSocket)
CRUD, scripting, tooling, debuggingHTTP REST :7070
Receive-only updates in a browserSSE :7072
Receive-only updates in a serviceUni WebSocket :7071
Commands and subscriptions on one connection, no libraryBidi WebSocket :7071/api/ws/v1/bidi
Lowest overhead on the JVM or in RustAeron SBE gateway (enable it first)

Details and spec links: API overview.

Step 3 — Use an embedded client library

Install (see embedded clients for the full list; versions shown are the latest release tags, 1.0.4; check the GitHub releases for newer):

LanguageInstall
JavaGradle: add the https://maven.pkg.github.com/bhf/aeron-cache-embedded repository, then implementation("com.aeron.cache:aeron-cache-embedded-client:1.0.4") (GitHub Packages needs a token with read:packages)
TypeScript.npmrc mapping the @bhf scope to GitHub Packages, then npm install @bhf/aeron-cache-embedded-client
Pythonpip install "git+https://github.com/bhf/aeron-cache-embedded.git@py-v1.0.4#subdirectory=libraries/python"
Rustaeron-cache-embedded-client = { git = "https://github.com/bhf/aeron-cache-embedded", tag = "rust-v1.0.4" }

Canonical pattern — create, hydrate a mirror, write, read locally (TypeScript; the other languages are shown on embedded clients):

const client = new AeronCacheClient("http://localhost:7070", "ws://localhost:7071");
await client.createCache("agent-demo");            // operationStatus CACHE_EXISTS is fine on re-run

const cache = new EmbeddedAeronCache(client, "agent-demo");
cache.subscribe(() => {}, undefined, undefined, true);   // hydrate a live local mirror
await cache.put("greeting", "hello");                    // remote write
console.log(cache.getLocal("greeting"));                 // local read, no round-trip

Where each capability lives in the clients:

NeedClient call
JSON merge patchpatchItem / patch_item; use EmbeddedObjectCache (EmbeddedObjects in Rust) so PATCH_ITEM deltas merge locally
CounterscreateCounterCache, getCounterCache, then put / increment / decrement / set
TTLputTimedItem; cancelItemRemoval; getTimers
BatchbulkOps — mix cache and counter ops, each with its own requestId
InspectiongetCaches, getStats
One connection for everythingAeronBidiClient("ws://localhost:7071")
Native Aeron transport (Java, Rust)AeronGatewayClient (requires AERON_GATEWAY_ENABLED=true)

Gotchas for agents:

  • A plain EmbeddedAeronCache stores opaque strings and ignores PATCH_ITEM (and rejects patch-mode subscriptions). Use the object cache for JSON values you patch.
  • Method names differ by language convention (getLocal / get_local; Rust uses insert). Check the language’s README and the samples/ directory in aeron-cache-embedded rather than assuming.
  • The TypeScript README in the library repo shows a different package name and some older method names; the table above and embedded clients reflect the published GitHub Packages name. When in doubt, read the library’s own package.json and tests.
  • Runnable examples for sync, async, streaming and bulk exist in every language under samples/. Copy from them.

Step 4 — Use the APIs directly

All examples assume the default local server. Replace localhost as needed.

HTTP — caches

# Create (body: cacheId). Re-running returns operationStatus CACHE_EXISTS.
curl -s -X POST http://localhost:7070/api/v1/cache \
  -H 'Content-Type: application/json' -d '{"cacheId":"agent-demo"}'

# Put (key + value, both strings)
curl -s -X POST http://localhost:7070/api/v1/cache/agent-demo \
  -H 'Content-Type: application/json' -d '{"key":"user:1","value":"{\"name\":\"Ada\",\"tier\":\"free\"}"}'

# Get one item / get all items
curl -s http://localhost:7070/api/v1/cache/agent-demo/user:1
curl -s http://localhost:7070/api/v1/cache/agent-demo

# Patch (RFC 7386 JSON Merge Patch — only "tier" changes; null deletes a field)
curl -s -X PATCH http://localhost:7070/api/v1/cache/agent-demo/user:1 \
  -H 'Content-Type: application/json' -d '{"value":"{\"tier\":\"pro\"}"}'

# Put with a TTL in milliseconds; cancel a scheduled removal
curl -s -X POST http://localhost:7070/api/v1/cache/timed/agent-demo \
  -H 'Content-Type: application/json' -d '{"key":"session","value":"active","ttl":60000}'
curl -s -X POST http://localhost:7070/api/v1/cache/agent-demo/session/cancel-removal

# Remove an item / clear a cache / delete a cache
curl -s -X DELETE http://localhost:7070/api/v1/cache/agent-demo/user:1
curl -s -X PATCH  http://localhost:7070/api/v1/cache/agent-demo
curl -s -X DELETE http://localhost:7070/api/v1/cache/agent-demo

# Inspection
curl -s http://localhost:7070/api/v1/caches
curl -s http://localhost:7070/api/v1/stats
curl -s http://localhost:7070/api/v1/timers

Note PATCH /api/v1/cache/{cacheId} (no key) clears the cache; PATCH .../{cacheId}/{key} merge-patches one value. Do not mix them up.

HTTP — counters

Counters are 64-bit integers under /api/v1/counters/...:

curl -s -X POST http://localhost:7070/api/v1/counters/ \
  -H 'Content-Type: application/json' -d '{"cacheId":"hits"}'                        # create counter cache
curl -s -X POST http://localhost:7070/api/v1/counters/hits \
  -H 'Content-Type: application/json' -d '{"key":"page","value":10}'                 # put
curl -s -X POST http://localhost:7070/api/v1/counters/increment/hits \
  -H 'Content-Type: application/json' -d '{"key":"page","amount":5}'                 # increment
curl -s -X POST http://localhost:7070/api/v1/counters/decrement/hits \
  -H 'Content-Type: application/json' -d '{"key":"page","amount":3}'                 # decrement
curl -s -X POST http://localhost:7070/api/v1/counters/set/hits \
  -H 'Content-Type: application/json' -d '{"key":"page","value":100}'                # set
curl -s http://localhost:7070/api/v1/counters/hits/page                              # get
curl -s http://localhost:7070/api/v1/counters-caches                                 # list
curl -s http://localhost:7070/api/v1/counters-stats                                  # stats

HTTP — bulk operations

One request, many operations, mixing cache and counter ops. Each operation carries its own requestId; responses echo it and come back in request order. Operation types include CREATE_CACHE, ADD_ITEM, PATCH_ITEM, GET_ITEM, REMOVE_ITEM, CLEAR_CACHE, DELETE_CACHE, CREATE_COUNTER_CACHE, ADD_COUNTER, INCREMENT_COUNTER, DECREMENT_COUNTER, SET_COUNTER, GET_COUNTER, CANCEL_ITEM, CANCEL_COUNTER (full enum: BulkOperationType in the spec).

curl -s -X POST http://localhost:7070/api/v1/cache/bulkops \
  -H 'Content-Type: application/json' -d '{
    "requestId": "batch-1",
    "operations": [
      {"operationType":"ADD_ITEM","requestId":"op-1","cacheId":"agent-demo","key":"k1","value":"v1"},
      {"operationType":"GET_ITEM","requestId":"op-2","cacheId":"agent-demo","key":"k1"}
    ]}'
# -> {"requestId":"batch-1","operationResponses":[{"requestId":"op-1","status":"SUCCESS",...},{"requestId":"op-2",...,"value":"v1"}]}

Inspect each entry’s status — a batch can partially succeed. See bulk operations.

SSE — receive-only streaming

curl -N "http://localhost:7072/api/sse/v1/cache/agent-demo"                       # live updates, one cache
curl -N "http://localhost:7072/api/sse/v1/cache/hydrate/agent-demo"               # replay current state first
curl -N "http://localhost:7072/api/sse/v1/caches/agent-demo,other?mode=patch&keys=agent-demo:user:1"

Counters use /api/sse/v1/counter/... and /api/sse/v1/counters/... (plus hydrate variants). The keys filter takes cacheId:key tokens or bare keys; mode is full (default) or patch (cache-only).

WebSocket — receive-only streaming

Same routes and query parameters on ws://localhost:7071/api/ws/v1/cache/{cacheId}, /api/ws/v1/caches/{cacheIds}, and the hydrate variants (counters: /api/ws/v1/counter/...). Each message is a JSON CacheUpdateEvent:

{"cacheId":"agent-demo","eventType":"ADD_ITEM","itemKey":"user:1","itemValue":"...","requestId":"..."}

eventType is one of ADD_ITEM, PATCH_ITEM, REMOVE_ITEM, CLEAR_CACHE, DELETE_CACHE. Apply them to a local map as: ADD_ITEM → set; PATCH_ITEM → deep-merge (RFC 7386) into the existing JSON value; REMOVE_ITEM → delete the key; CLEAR_CACHE / DELETE_CACHE → clear the map.

Bidirectional WebSocket — commands and subscriptions on one socket

Connect to ws://localhost:7071/api/ws/v1/bidi. All frames are JSON text with a type. Generate a unique correlationId per request; the server echoes it on every response.

// client -> server: a command
{"type":"command","correlationId":"c1","op":"CREATE_CACHE","cacheId":"agent-demo"}
{"type":"command","correlationId":"c2","op":"ADD_CACHE_ENTRY","cacheId":"agent-demo","key":"k","value":"v"}
{"type":"command","correlationId":"c3","op":"GET_CACHE_ENTRY","cacheId":"agent-demo","key":"k"}
// server -> client
{"type":"commandResponse","correlationId":"c3","status":"SUCCESS","cacheId":"agent-demo","key":"k","value":"v"}

// subscribe (sendSnapshot replays current state as ADD_ITEM); wait for the "subscribed" ack
{"type":"subscribe","correlationId":"s1","sendSnapshot":true,"caches":[{"cacheId":"agent-demo","mode":"FULL"}]}
// server -> client
{"type":"subscribed","correlationId":"s1","cacheIds":["agent-demo"]}
{"type":"streamUpdate","correlationId":"s1","eventType":"ADD_ITEM","cacheId":"agent-demo","key":"k","value":"v"}

Rules that trip agents up:

  • Client frames: command, subscribe, unsubscribe, bulk. Server frames: commandResponse, streamUpdate, entries, stats, timers, bulkResponse, subscribed, error.
  • Multi-part replies (entries, stats, timers, bulkResponse) arrive as several frames with the same correlationId; accumulate until endOfBatch: true.
  • The subscribed ack is a barrier: don’t issue mutations you expect to see streamed back until it arrives.
  • Command ops are named differently from bulk ops (ADD_CACHE_ENTRY, INCREMENT_COUNTER_ENTRY, … vs ADD_ITEM, INCREMENT_COUNTER). Use the WsOp enum for command frames and BulkOperationType for entries inside bulk frames.
  • A frame that cannot be parsed returns an error with correlationId: null.
  • In streamUpdate, counter values are JSON numbers; cache values are strings.

Aeron SBE gateway

Binary, schema id 7, UDP :7075/:7076 or IPC, disabled by default. Do not hand-roll this: use the Java or Rust AeronGatewayClient. If you must generate code, build from gateway-schema.xml with the SBE tool. Background: Aeron + SBE transport.

Specs to read instead of guessing

SurfaceFile in bhf/aeron-cache
HTTP cachescache-http/openapi.yml
HTTP counterscache-http/counters-openapi.yml
WebSocket (uni)cache-ws/ws-openapi.yaml, cache-ws/counters-ws-openapi.yaml
WebSocket (bidi)cache-ws/bidi-ws-openapi.yaml
SSEcache-sse/sse-openapi.yaml, cache-sse/counters-sse-openapi.yaml
SBE schemacache-aeron-gateway/aeron-gateway-server/src/main/resources/sbe/gateway-schema.xml

Troubleshooting checklist

SymptomLikely cause
Connection refused on 7070Backend not running; check ~/.aeron-cache/aeron-cache.log
WebSocket/SSE connection refusedGateway disabled (WEBSOCKET_GATEWAY_ENABLED / SSE_GATEWAY_ENABLED), or wrong port (WS 7071, SSE 7072)
UNKNOWN_CACHECache not created yet
UNKNOWN_KEYKey missing or already expired (TTL)
CACHE_EXISTSCreate called twice — usually safe to ignore
getLocal returns nothing right after a writeMirror not hydrated, or the stream event hasn’t arrived yet; wait for hydration / subscribed
Patched value not reflected locallyUsing a plain EmbeddedAeronCache; use the object cache
Aeron gateway won’t connectAERON_GATEWAY_ENABLED=false (the default), or media mismatch (udp vs ipc; IPC needs the same host and AERON_DIR)
503 from a gatewayCluster not connected yet — retry shortly

Generating a client for a new language

If your language has no embedded client, the aeron-cache-embedded repository ships agent instructions for scaffolding one:

instructions/library-generation.instructions.md

Give that file to your agent as its task brief. It is the maintained source of truth, so link to it rather than copying it, and read it at generation time: it changes as the libraries do. In summary, it asks for:

  1. A low-level client (AeronCacheClient) taking the HTTP base URL (http://localhost:7070) and WebSocket URL (ws://localhost:7071). It covers the full operation surface: string caches, counter caches, bulk, inspection, and subscriptions with hydrate, keyed and patch modes. It provides sync and async variants where the language has both.
  2. Embedded abstractions with a local mirror: EmbeddedAeronCache (strings), EmbeddedCounterCache (int64) and EmbeddedObjectCache (JSON objects, deep-merging PATCH_ITEM deltas with RFC 7386). The string cache ignores PATCH_ITEM and rejects patch-mode subscriptions.
  3. Transports: HTTP+WebSocket and the bidirectional WebSocket (AeronBidiClient) are required. The Aeron SBE gateway is optional, only where a mature Aeron client exists (currently Java and Rust), with codecs generated from the shared SBE schema.
  4. Behaviour: operationStatus mapped into responses (business outcomes never thrown), transport failures raised, batched responses reassembled until the end-of-batch marker, and WebSocket reconnect with re-subscribe.
  5. Parity with the reference clients: the instructions tell the agent to read the existing code under libraries/ and samples/ first. Python is the most readable reference for the full surface, and Rust for the Aeron gateway. A complete language ships sync-sample, async-sample, streaming-sample, bulk-sample and bidi-sample under samples/<language>/ (plus aeron-sample if it implements the gateway).

Acceptance checklist for a generated library, to run against a local monolith:

  1. Create a cache, then put, get, patch, remove, clear and delete, asserting operationStatus each time.
  2. Subscribe with hydration; a write from a second client appears in getLocal without a network call.
  3. PATCH_ITEM through the object cache reconstructs the full merged object.
  4. Counter increment, decrement and set round-trip.
  5. A bulk batch mixing cache and counter operations returns results in request order.
  6. The bidi client reassembles multi-frame replies (entries, stats, timers, bulkResponse) and honours the subscribed barrier.
  7. The samples listed above exist and run, and CI matches the other libraries.

Embedded clients · API overview · Getting started · CLI · Streaming subscriptions · Bulk operations