TTemporalStore.AI GitHub

Deployment

Start local in one command; scale to distributed when you need it.

TemporalStore runs the same engine two ways. Local mode is a single node on local disk — perfect for development, edge, and self-hosting. Distributed mode fans the same serving core across a proxy, a metaserver, and many datanodes, with replicated durability and, at enterprise scale, shared storage.

Local mode

One process, one data directory, local disk durability. Best for development, CI, edge, demos, and single-tenant self-hosting.

Distributed mode

Proxy, metaserver, and datanodes. Best when you need horizontal read/write scale, failover, rolling upgrades, and shard movement.

Enterprise scale

MatrixObject shared storage separates compute from durable state, so datanodes can scale elastically while retention lives in the object tier.

Choose a topology

Start with the smallest mode that satisfies durability and scale.

Both modes expose the same client API and data models. The topology underneath changes; your app code should not.

NeedRun thisWhy
Development, CI, demos, edge, or a single-tenant self-hostLocal modeOne container or binary, one mounted data directory, no control plane.
High availability and failoverDistributed + MatrixRaftEach shard has co-located replicas and quorum-committed WAL durability.
Many concurrent entities, services, or tenantsDistributed modeAdd datanodes for shard capacity and proxies for client fan-in.
Compute/storage separation and elastic retentionDistributed + MatrixObject EnterpriseDatanodes become elastic compute over one shared durable object tier.

Installation

Three ways to get TemporalStore.

Run the prebuilt container, build the Rust engine from source, or install the Python client into your app. Most people start with the Docker image to run the server and add the Python client for their agent code — the two work together.

MethodBest forCommand
Docker imageRunning the server — dev, edge, productiondocker run … ghcr.io/matrixarkai/temporalstore:latest
From source (Cargo)Building the Rust engine, contributing, custom buildscargo build --release
Python client (pip)Apps and agents that write events and read contextpip install temporalstore

Prerequisites

  • A container runtime (Docker or Podman) for the image path — or a Rust toolchain (stable, 1.75+) to build from source. Nothing else is required to run the server.
  • Python 3.9+ if you use the client SDK from your application or agent.
  • Optional: Ollama for fully local, open-source embedding and reader models with no API keys. Hosted providers work too.
  • Ports: 8080 serves the client API in local mode; in distributed mode the metaserver defaults to 9100 and the proxy exposes 8080.
Docker — the fast path
docker run -d --name temporalstore \
  -p 8080:8080 \
  -v $PWD/ts-data:/var/lib/temporalstore \
  ghcr.io/matrixarkai/temporalstore:latest \
  --mode local --data-dir /var/lib/temporalstore
From source with Cargo (the Rust engine)
git clone https://github.com/matrixarkai/TemporalStore
cd TemporalStore
cargo build --release

# the server binary lands in target/release
./target/release/temporalstore --mode local \
  --data-dir ./ts-data --port 8080
Python client for apps and agents
pip install temporalstore

# then, in your application
from temporalstore import Client
ts = Client("http://localhost:8080")

Quickstart

From zero to a context pack in five steps.

The minimal end-to-end flow: start the server, point it at local models, write a couple of events, build a summarized context pack, and read it back. This is the “hello world” of agent memory on TemporalStore.

1–2 · Start the server and wire local models (no API keys)
# 1. start the single-node server
docker run -d --name temporalstore -p 8080:8080 \
  -v $PWD/ts-data:/var/lib/temporalstore \
  ghcr.io/matrixarkai/temporalstore:latest --mode local

# 2. pull open-source models and point the server at them
ollama pull nomic-embed-text
ollama pull qwen2.5:1.5b
export TS_EMBED_URL=http://127.0.0.1:11434
export TS_EMBED_MODEL=nomic-embed-text
export TS_READER_URL=http://127.0.0.1:11434/v1
export TS_READER_MODEL=qwen2.5:1.5b
3–5 · Write events, build a pack, read it back
from temporalstore import Client
ts = Client("http://localhost:8080")

# 3. write a couple of events
ts.put_event(table="agent_memory", entity="workspace_7", ts_ms=now_ms,
             attrs={"kind": "decision", "text": "chose Postgres over DynamoDB"})
ts.put_event(table="agent_memory", entity="workspace_7", ts_ms=now_ms,
             attrs={"kind": "note", "text": "latency budget is 50ms p99"})

# 4. build a summarized, token-budgeted context pack
pack = ts.context(entity="workspace_7", since="24h",
                  summarize=True, token_budget=4096)

# 5. read it back
print(pack.text)      # the assembled context
print(pack.tokens)    # how much of the budget it used

The same five steps work against a distributed cluster — only the endpoint changes (point the client at the proxy). Your application code is identical in both modes.

Open-source models

Local embeddings and a local reader, no API keys required.

TemporalStore needs an embedding model to index and retrieve events by meaning, and it can use a reader model (an LLM) to summarize retrieved history into a compact context pack. Both run fully local through Ollama — nothing leaves the box — or you can drop in a hosted provider through the very same environment variables.

Pull the models and wire the env
# embeddings for retrieval + a small local reader for summaries
ollama pull nomic-embed-text
ollama pull qwen2.5:1.5b

# the embedding model indexes and searches events
export TS_EMBED_URL=http://127.0.0.1:11434
export TS_EMBED_MODEL=nomic-embed-text

# the reader model condenses retrieved history into the pack
export TS_READER_URL=http://127.0.0.1:11434/v1
export TS_READER_MODEL=qwen2.5:1.5b
RoleOSS default (Ollama)AlternativesEnv vars
Embedding
indexes & retrieves
nomic-embed-text all-MiniLM-class or bge-small for a lighter footprint; hosted text-embedding-3 or Voyage as OpenAI-compatible drop-ins. TS_EMBED_URL, TS_EMBED_MODEL
Reader / LLM
summarizes packs
qwen2.5:1.5b qwen2.5:7b for higher-quality summaries; any OpenAI-compatible chat endpoint (OpenAI, or your own gateway). TS_READER_URL, TS_READER_MODEL

Picking a size. The embedding model is the one that matters for retrieval quality; a compact model such as nomic-embed-text is a strong default and keeps indexing cheap. The reader only shapes the summary, so a small 1.5b model is fine for most agent memory — step up to 7b when you want richer rollups and can spend the extra latency. On CPU-only boxes, keep the reader small; on a GPU you can run the larger reader comfortably.

Hosted is optional. Point TS_EMBED_URL and TS_READER_URL at any OpenAI-compatible endpoint to use a hosted provider instead — the store treats local and hosted models identically, so you can start fully local and switch later without touching application code.

Two modes, one engine

The same API and data models; the topology grows underneath.

Your application code does not change between modes — it writes events and builds context packs the same way. What changes is how many processes serve those calls and where durable data lives.

Local mode — single node
Your app / agentSDK or HTTP
↓
TemporalStore (single process)serving core + WAL + local cache
↓
Local diskblocks, bands, wal, snapshots
Distributed mode — clustered
Apps / agentsmany clients
↓
Proxyrouting, batching
Metaservershards & placement
Datanodeshard A
Datanodeshard B
Datanodeshard C
↓ durability ↓
MatrixRaftreplicated WAL
MatrixObjectshared storage Ent

Same clients, same models. Local mode is one process on local disk; distributed mode spreads shards across datanodes with replicated or shared durable storage.

Local mode

One node, local disk, running in a minute.

Use local mode for development, CI, edge deployments, single-tenant self-hosting, and trying the store on your own data. It needs nothing but a container runtime; add Ollama if you want fully local, open-source embeddings and readers.

Run the single-node server (Docker)
docker run -d --name temporalstore \
  -p 8080:8080 \
  -v $PWD/ts-data:/var/lib/temporalstore \
  ghcr.io/matrixarkai/temporalstore:latest \
  --mode local --data-dir /var/lib/temporalstore
Fully local models with Ollama (open source)
# embeddings + a small local reader, no API keys
ollama pull nomic-embed-text
ollama pull qwen2.5:1.5b

export TS_EMBED_URL=http://127.0.0.1:11434
export TS_READER_URL=http://127.0.0.1:11434/v1
Write an event and build a context pack
from temporalstore import Client
ts = Client("http://localhost:8080")

ts.put_event(table="agent_memory", entity="workspace_7",
             ts_ms=now_ms, attrs={"kind": "decision", "text": "..."})

pack = ts.context(entity="workspace_7", since="24h",
                  summarize=True, token_budget=4096)

Inside the single node

One process, one data directory.

In local mode the serving core, the write-ahead log, and the hot cache all live in a single process. Every durable byte lands under one data directory, so persistence is simply a volume mount and a backup is a directory copy.

In-process
Serving coreAPI, models, context builder
↓
WALappend-first durability
Local cachehot working set in memory
Data dir — /var/lib/temporalstore
wal/append log, replayed on start
blocks/materialized pages
bands/time-ordered runs
slabs/sealed segments
snapshots/compacted checkpoints

Writes hit the WAL first, then materialize into blocks, bands, and slabs; snapshots compact the tail. Mount the data dir on a persistent volume and the whole node survives restarts.

A fuller local configuration (env + flags)
# where durable data lives (mount this on a real volume)
export TS_DATA_DIR=/var/lib/temporalstore
export TS_PORT=8080

# open-source models via Ollama -- no API keys
export TS_EMBED_URL=http://127.0.0.1:11434
export TS_EMBED_MODEL=nomic-embed-text
export TS_READER_URL=http://127.0.0.1:11434/v1
export TS_READER_MODEL=qwen2.5:1.5b

# size the hot working set to available RAM
export TS_CACHE_BYTES=2GiB          # in-memory block cache
export TS_WAL_FSYNC=batch           # batch fsync for throughput

temporalstore --mode local \
  --data-dir "$TS_DATA_DIR" --port "$TS_PORT"
Resource guidance It fits on a laptop Give memory to the hot working set (a couple of GiB covers most single-tenant workloads) and give disk to durable data — the data dir grows with retained history, not with RAM. CPU tracks embedding/reader calls, so a small local model keeps it light.
Backup & restore Copy the data dir Snapshot or tar the data directory (WAL + blocks/bands/slabs + snapshots) while the node is quiesced or from a volume snapshot. Restore is the reverse: drop it in place and start the binary — WAL replay reconstructs any un-materialized tail.
Upgrade Swap the binary Stop the old process, start the new binary on the same data dir. On boot it replays the WAL and installs the latest manifest, so an in-place version bump needs no export/import. Keep a data-dir copy first for a one-step rollback.

When local mode is enough. Development, CI, demos, edge and on-device context, and single-tenant self-hosting all run comfortably on one node — the API and data models are identical to distributed mode, so nothing about your application code has to change later.

Its limits. One process means no high availability: if the node is down, the store is down, and you scale it only vertically (bigger box, more disk). When you need failover or horizontal throughput across many entities, move to distributed mode below — same API, same models.

Distributed mode

Scale reads and writes across a cluster.

Distributed mode adds a proxy (routing and batching), a metaserver (shard placement and membership), and multiple datanodes that each own a set of shards. Durability is provided by MatrixRaft (consensus-replicated WAL) or, at enterprise scale, by MatrixObject shared storage so compute and storage scale independently.

A minimal cluster (docker-compose)
services:
  metaserver:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role metaserver --listen 0.0.0.0:9100
  datanode-a:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role datanode --meta metaserver:9100 --shards 0-341
  datanode-b:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role datanode --meta metaserver:9100 --shards 342-683
  proxy:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role proxy --meta metaserver:9100
    ports: ["8080:8080"]

Clients still talk to a single endpoint (the proxy) and use the same API as local mode. Add datanodes to grow capacity; the metaserver rebalances shards. Choose the durability backend below.

Roles & how they interact

A control plane that places shards; a data plane that serves them.

Three roles divide the work. The proxy is stateless — it caches the routing table and forwards each call to the right datanode. The metaserver is the control plane: it owns the shard map, placement, membership, and leases. The datanodes are the data plane: each owns a set of shards, runs the model executors, and appends to its own WAL.

Proxystateless, cached routing
Metaservershard map, placement, membership, leases
Datanodesown shards, model executors + WAL
1. Bring up the metaserverThe control plane starts first and holds the empty shard map.
2. Join datanodesEach datanode registers with the metaserver, which assigns it shards.
3. Start the proxyThe proxy pulls the routing table and caches it.
4. Clients connectApps hit the proxy on one endpoint; the metaserver stays off the hot path.

The metaserver is consulted on membership and placement changes, not on every request — the proxy’s cached routing table keeps reads and writes on a direct proxy→datanode path.

Sharding & placement

Keys hash to buckets; buckets group into shards; the metaserver places shards.

From key to datanode
Keyentity / table
↓ hash ↓
Bucketfixed hash space, many per shard
↓ group ↓
Shardunit of placement & replication
↓ assign ↓
Datanodeowns the shard’s buckets

Keys hash into a fixed space of buckets; many buckets group into a shard, which is the unit the metaserver places and replicates. Because the bucket space is fixed, adding a datanode does not reshuffle every key — the metaserver moves whole shards to the new node and updates the map, triggering a rebalance that the proxy picks up on its next routing-table refresh.

Replication & scaling

Choose durability, then scale each plane independently.

Two durability models back a shard. With MatrixRaft, each shard is a co-located full replica set that commits by quorum — storage travels with compute. With MatrixObject shared storage, one durable copy lives in the object tier and datanodes stay stateless, so compute and capacity scale apart. The infrastructure deep dive walks through append-structured storage and WAL replay; MatrixObject Enterprise covers the shared store.

MatrixRaft — co-located replicas
Replicaleader
Replicafollower
Replicafollower

Quorum commit; a failed replica is covered by the rest of the set.

Shared store — stateless datanodes
Datanodestateless
Datanodestateless
↓
Scale capacity & throughput Add datanodes More datanodes mean more shards served in parallel and more model-executor capacity. The metaserver rebalances shards onto the new nodes.
Scale connections Add proxies Proxies are stateless and share the cached routing table, so put several behind a load balancer to fan out client connections without touching the data plane.
Control plane Keep the metaserver light The metaserver handles placement and membership, not per-request traffic — it is off the hot path, so it does not scale with request volume.

Operations

Health, rolling upgrades, backup, and metrics.

ConcernHow it works
Health & readinessEach role exposes liveness and readiness endpoints; a datanode reports ready only once its shards are loaded and WAL replay has caught up.
Rolling upgradeDrain a datanode — the metaserver moves its shards (or promotes replicas) — then upgrade the binary and rejoin. Repeat node by node for a zero-downtime roll.
Backup (MatrixRaft)Back up per-shard from a follower snapshot; the replicated WAL preserves the commit tail.
Backup (shared store)The object tier holds the single durable copy — back up (or version) it centrally; datanodes carry no unique state.
ObservabilityPrometheus-style metrics for shard health, replica lag, WAL append latency, and cache hit ratio; scrape them per role to watch placement and durability.
A fuller cluster: 2 datanodes + proxy + metaserver, backend selected
services:
  metaserver:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role metaserver --listen 0.0.0.0:9100
    ports: ["9100:9100"]

  datanode-a:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role datanode --meta metaserver:9100 --shards 0-341
    environment:
      # --- pick ONE durability backend ---
      TS_STORAGE_BACKEND: raft            # co-located replicas, quorum commit
      # TS_SHARED_STORE_DIR: /mnt/matrixobject   # shared store: stateless datanodes
    volumes: ["dn_a:/var/lib/temporalstore"]

  datanode-b:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role datanode --meta metaserver:9100 --shards 342-683
    environment:
      TS_STORAGE_BACKEND: raft
    volumes: ["dn_b:/var/lib/temporalstore"]

  proxy:
    image: ghcr.io/matrixarkai/temporalstore:latest
    command: --role proxy --meta metaserver:9100
    ports: ["8080:8080"]

volumes: { dn_a: {}, dn_b: {} }

Set TS_STORAGE_BACKEND=raft for co-located replicated durability, or point TS_SHARED_STORE_DIR at the shared object tier for stateless datanodes. See the deep dive for how the backend is resolved and MatrixObject Enterprise for the shared store.

Storage backend

Pick the durable tier for your deployment.

BackendModeBest forLicensing
Local diskLocalSingle node, dev, edge, self-hosted.Open source
MatrixRaftDistributedReplicated high availability without shared storage; fixed replica set.Open source
MatrixObject EnterpriseDistributedDisaggregated, concurrent read/write, elastic capacity at five-nines. DetailsEnterprise

Configuration reference

The env vars and flags you will actually set.

Every setting has a sensible default; you only touch what your deployment needs. The same variables apply in local and distributed mode — distributed mode simply adds the role and cluster flags.

Variable / flagDefaultWhat it does
--mode / --rolelocalRun as a single-node local server, or as a metaserver / datanode / proxy in a cluster.
TS_DATA_DIR / --data-dir/var/lib/temporalstoreDirectory for all durable data (wal, blocks, bands, slabs, snapshots). Mount it on a persistent volume.
TS_PORT / --port8080Client API port for local mode and the proxy.
TS_STORAGE_BACKENDraftDistributed durability backend: raft for co-located replicas (quorum commit) or shared for a shared object tier.
TS_SHARED_STORE_DIRunsetPath/mount of the shared store when TS_STORAGE_BACKEND=shared; datanodes stay stateless. MatrixObject Enterprise.
TS_EMBED_URL / TS_EMBED_MODEL—Embedding endpoint (Ollama or OpenAI-compatible) and model name used for retrieval.
TS_READER_URL / TS_READER_MODEL—Reader/LLM endpoint and model used to summarize context packs. Optional — retrieval works without it.
TS_CACHE_BYTES2GiBSize of the in-memory hot block cache. Give it more RAM to keep a larger working set resident.
TS_WAL_FSYNCbatchDurability mode for WAL appends: batch for throughput, sync for per-write fsync.
--meta / --listen—Distributed wiring: --meta host:9100 tells a datanode/proxy where the metaserver is; --listen binds the metaserver.
--shards—Shard range a datanode owns (e.g. 0-341). The metaserver assigns ranges as nodes join.

Verify the install

Confirm the node is healthy and retrieval round-trips.

Two checks tell you everything: a health/readiness probe that the process is up and caught up, and a write→read round trip that proves indexing and retrieval work end to end.

Health and readiness
# liveness -- is the process up?
curl -s localhost:8080/healthz
# {"status":"ok"}

# readiness -- shards loaded and WAL replay caught up?
curl -s localhost:8080/readyz
# {"ready":true,"shards_loaded":true,"wal_replayed":true}
Round trip: write, then retrieve
from temporalstore import Client
ts = Client("http://localhost:8080")

ts.put_event(table="agent_memory", entity="verify_1", ts_ms=now_ms,
             attrs={"kind": "note", "text": "installation smoke test"})

pack = ts.context(entity="verify_1", since="1h")
assert "installation smoke test" in pack.text
print("OK -- write and retrieval round-trip succeeded")

A healthy node returns ok on /healthz immediately and ready:true on /readyz once shards are loaded. If /readyz stays not-ready, the node is still replaying its WAL — give it a moment on large data dirs. In a cluster, probe each role; the proxy reports ready only when it has pulled the routing table.

Which mode should I run?

Start local; go distributed when one node is not enough.

If you need…Mode
Development, CI, a demo, or a single-tenant self-hostLocal
Edge or on-device context with no cluster to runLocal
High availability and failover for agent memoryDistributed + MatrixRaft
Horizontal scale for concurrent reads/writes across many entitiesDistributed
Disaggregated compute/storage, elastic retention, five-ninesDistributed + MatrixObject Enterprise

Client SDKs & agent integrations

Talk to the store from your app — or let your agent do it automatically.

Applications use the Python client (pip install temporalstore) to write events and build context packs against a local node or a cluster proxy. For coding agents, TemporalStore ships Claude Code and Codex plugins/hooks that ingest an agent’s turns as events and retrieve the relevant context back automatically — no glue code, no manual bookkeeping.

The hooks run alongside your agent session: they append each turn to the store and, on the next turn, pull a fresh context pack and inject it — so long-running work carries its own memory. Both plugins fail open (if the store is unreachable the agent keeps running) and use the same local or hosted models described above. Install instructions live in the GitHub repository.

Source & pluginsGitHub repositoryRust engine, Python client, and the Claude Code + Codex memory plugins. Tech & InfraHow it fits togetherServing core, caches, tiering, and storage backends. BenchmarksLatency & quality evidenceContext token savings, retrieval quality, and scale numbers.

Cloud API Enterprise

Ingest and retrieve over HTTP — your own apps, at high QPS.

Beyond the agent plugins, enterprise customers connect their own applications to a managed, multi-tenant HTTPS endpoint — ingesting resources and skills through APIs rather than hooks. Push events in; get back a ranked, token-budgeted context pack. Every route is authenticated with a per-tenant API key over TLS, and the ingest path is asynchronous so high-QPS producers never block on durability.

◗ Managed endpoint coming soon. api.temporalstore.ai is being stood up. The same API runs today on a self-hosted cluster — point your client at the proxy (see Distributed mode) and use the routes below.

EndpointPurposeShape
POST /v1/ingestWrite resources, skills, session eventsasync 202; batch up to 1,000 records
POST /v1/session/commitClose a window; extract entities & summariesone pass over the session
POST /v1/retrieveRanked, token-budgeted ContextPackread path; p50 < 2 ms
PUT / GET /v1/blob/<key>Large attachmentsstreamed to shared storage
POST /v1/mcpModel Context Protocol over HTTPfor MCP-native clients
GET /v1/healthz · /readyzLiveness / readinessprobes
Ingest resources and skills — async, fast-ack (202)
curl -sS https://api.temporalstore.ai/v1/ingest \
  -H 'authorization: Bearer sk_live_...' \
  -H 'content-type: application/json' \
  -d '{"scope":"acme/agent-7/session-42","records":[
        {"type":"resource_chunk","uri":"repo://api/handler.rs","text":"pub async fn handle(...)"},
        {"type":"skill_section","name":"deploy-runbook","text":"1. drain 2. roll 3. verify"}]}'
# -> 202 Accepted   {"accepted": 2}
Retrieve a ranked, token-budgeted context pack
curl -sS https://api.temporalstore.ai/v1/retrieve \
  -H 'authorization: Bearer sk_live_...' \
  -H 'content-type: application/json' \
  -d '{"query":"current staging build and how to roll it",
       "scope":"acme/agent-7","token_budget":1800}'
# -> 200 OK  {"pack":[{"text":"staging = 1.9.2","source":"session-42#evt-8"}, ...],
#             "tokens":214}
Stream large attachments straight to shared storage
curl -sS -X PUT --data-binary @report-q3.pdf \
  https://api.temporalstore.ai/v1/blob/acme/report-q3.pdf   # chunked upload
curl -sS https://api.temporalstore.ai/v1/blob/acme/report-q3.pdf  # streamed back

Large files stream directly into the MatrixObject Enterprise shared-storage tier via append_blob, so they never bloat the hot ingest path. Tenant metadata (accounts, keys, scopes) is stored as KV inside TemporalStore itself by default; MatrixKV is an optional drop-in for a transactional metadata plane. Self-hosted clusters expose the same operations through the proxy — only the endpoint changes.

Auth, rate limits & quotas

Per-tenant keys, token-bucket limits, contract-raised defaults.

Each request carries a per-tenant bearer key over TLS 1.2+, scoped to one or more namespaces and rotated from the tenant portal — keys never appear in URLs. Regional endpoints (api.us.temporalstore.ai, api.eu.temporalstore.ai) keep data in-region. Limits are enforced per key with a token bucket, reported in X-RateLimit-* headers, and answered with 429 + Retry-After when exceeded. The defaults below are the enterprise baseline and are raised per contract — throughput scales horizontally as datanodes are added.

Limit / quotaDefaultNotes
POST /v1/ingest5,000 req/s · 10,000 burstper tenant key
POST /v1/retrieve6,000 req/s · 12,000 burstread path
Mixed ingest + retrieve~5,000 ops/s per 8-core nodelinear scale-out
Max attachment (/v1/blob)5 GBstreamed — no client-memory cap
Max ingest batch1,000 records / 16 MB bodywhichever first
Storage per tenant1 TB (expandable)records + attachments

Defaults are soft and set per enterprise contract. Exceeding a quota returns a machine-readable 413 (payload) or 507 (storage) — ingest never silently drops.

Keep reading

Go deeper on the engine.

Tech & InfraArchitecture & internalsServing core, caches, tiering, and storage. Deep essayServing engine internalsStorage layout, replication, and recovery. BenchmarksScale & quality evidenceLatency, parity, and context quality.