GitLoom

Limits and behaviour

What is asynchronous, how fast retrieval actually is, what the vectors are, and what is not enforced yet.

This page is the one that saves you an afternoon. It documents what GitLoom actually does today, including the parts that are not finished. Where a number is measured, it says so and against what. Where something is not enforced yet, it says that too, rather than describing an intention as a behaviour.

Writes are asynchronous

POST /v1/memories returns 202 Accepted. The memory does not exist yet.

{"id":"3f2b9e01-7c44-4d1a-9f6e-0a1b2c3d4e5f","namespace":"user-8213","status":"accepted"}

202 rather than 200 because extraction is several model calls against a conversation. Claiming 200 would make the next read look broken.

What happens between the 202 and the memory existing:

  1. The request is validated — credential, namespace exists, at least one message — and enqueued. This is where a rejection happens, while you are still listening.
  2. A worker picks it up, fetches the namespace's repository, and runs extraction, cue-writing, relation-linking, and reconciliation.
  3. New and rewritten memories are committed to git, embedded, packed, and pushed back to object storage with a compare-and-swap.
  4. The namespace's recorded head advances. From that instant, retrieval returns the new memories.

How long that takes

There is no published SLA, and you should not build a UI that depends on a specific figure. What is measurable:

There is no way to poll for completion

This is the sharpest edge in the current API, so it is worth stating plainly:

What to do instead. Do not write-then-immediately-read in the same user turn and expect the memory back. Write at the end of a turn and retrieve at the start of the next one; by then, ingestion has almost always finished. If you genuinely need to confirm, poll /v1/retrieve with a query you know the new content should match, with a backoff, and give up gracefully — retrieval is cheap, but it is not free.

Ordering and duplicate suppression

A partial failure that is not visible to you

If extraction succeeds but embedding fails, the memories are committed to git anyway and the failure is logged. Those memories are retrievable immediately by the lexical and graph arms, but not by semantic search, until the next successful write to that namespace fills the missing vectors in. Failing the whole job instead would re-run — and re-charge for — extraction that already worked.

The practical symptom: a memory that a keyword query finds but a paraphrased question does not, which starts working after the user's next session is ingested.

Retrieval latency

GET /v1/retrieve makes no model call except the query embedding.

Measured against the deployed stack over 200 authenticated requests:

p50 p99 max
Server-side retrieval (RetrievalMillis) 109 ms 145 ms 152 ms
Client end-to-end, India → ap-south-1 211 ms 383 ms 765 ms

Read that table carefully, because the two rows mean different things:

Roughly 100 ms of the server-side figure is the query embedding — a network round trip to Bedrock, not search. For comparison, before an embedder was wired the same measurement was p50 7.4 ms / p99 19.6 ms. So:

vector_ms, lexical_ms and graph_ms are returned on every response so you can see this yourself rather than take our word for it.

Cold starts and cache behaviour

Retrieval defaults

limit default 24 hits
Graph expansion 1 hop, from the top 6 hits
Expired incidents excluded
Empty result hits is absent, not []
Namespace listing first 200 namespaces

Embeddings

Model Amazon Titan Text Embeddings v2 (amazon.titan-embed-text-v2:0)
Dimensions 1024
Normalised yes
Symmetric yes — queries and stored text are embedded identically
What is embedded cues, not memory bodies
Region ap-south-1, via Bedrock

Why Titan and not Cohere, which would batch better: Cohere on Bedrock is a Marketplace model requiring an account-level subscription accepted in the console — IAM permissions alone do not unlock it. Titan is Amazon-owned and works with nothing but bedrock:InvokeModel. Both produce 1024 dimensions, so the stored vector width does not change if that decision is revisited.

Two consequences worth knowing:

Quotas

Per-account quotas are enforced. Every account has a plan — free if nothing was ever chosen — and every write, retrieval, playground chat, and stored-memory count is metered against that plan's monthly limits. See Plans and pricing for the numbers themselves; this section is about the mechanics.

{"error":{"code":"quota_exceeded","message":"quota: limit reached: writes (100 per month)"}}
{"error":{"code":"balance_exhausted","message":"quota: balance exhausted: reads allowance spent and the wallet cannot cover the overage"}}
{"error":{"code":"rate_limited","message":"quota: rate limited: the free plan allows 30 reads per minute"}}

A notification fires internally when a meter crosses 80% of its limit, so an account approaching a ceiling is not surprised by it.

Infrastructure ceilings, shared across every account

Separate from per-account quotas, these are platform-wide limits — they exist so no single account's traffic can starve every other account's, and they are not sized per plan:

limit value what you see when you hit it
Concurrent retrievals, whole platform 20 Lambda throttles; the gateway returns 5xx. Retry with backoff.
Concurrent ingestion workers, whole platform 5 Nothing — the queue absorbs it. Your write takes longer to land.
Ingestion worker timeout 15 min The message is retried, then dead-lettered.
Queue retention 4 days A message not processed in 4 days is dropped.
Namespace name [a-z0-9-], ≤64 chars 400 invalid_namespace
Namespaces returned by GET /v1/namespaces 200 The list is truncated silently.
Credential cache at the gateway 30 s See below.
Recharge, one transaction ₹500 min, ₹1,00,000 max 400 below_minimum / 400 above_maximum

Revocation takes up to 30 seconds. Authorization results are cached for 30 seconds so that an agent calling with the same key every turn does not pay a lookup each time. A revoked key therefore keeps working for up to half a minute. Treat revocation as "stops within a minute", not "stops instantly", and rotate anything genuinely compromised with that in mind.

Errors

The error body has two shapes, which is an inconsistency you have to code around today:

Everything except retrieval:

{"error":{"code":"namespace_not_found","message":"create namespace \"user-8213\" before writing to it"}}

GET /v1/retrieve:

{"error":"namespace not found","namespace":"user-8213"}

Branch on code where it exists and on the HTTP status everywhere. The message text is prose and may be reworded; the codes are stable.

status code meaning
400 invalid_body body missing or not valid JSON
400 invalid_namespace namespace name outside [a-z0-9-]{1,64}
400 no_messages messages was empty
400 invalid_env key env was not live or test
401 missing, malformed, expired or revoked credential
403 dashboard_only key management attempted with an API key
404 namespace_not_found write to a namespace that was never created
404 not_found no such key, or no such route
500 internal logged our side; safe to retry

A 401 is deliberately uninformative. Distinguishing "no such key" from "wrong secret" tells an attacker which half of a guess was right, so both fail identically — and in constant time.

Environments: live and test are not isolated

A key carries an environment in its prefix, gl_live_ or gl_test_, and GET /v1/whoami reports it. The environment is not currently a data boundary. A gl_test_ key reads and writes exactly the same namespaces as a gl_live_ key on the same account.

The prefix is useful today for two real things: a production credential pasted into a test config is obvious on sight, and secret scanners can match it. It is not a sandbox. If you need one, use a separate namespace — that is a hard boundary — or a separate account.

Browsers

The API's CORS policy allows GET and OPTIONS only, from the dashboard's origins. A browser cannot POST /v1/memories, POST /v1/namespaces, or manage keys.

That is a limitation, but the security advice would be identical without it: an API key is account-wide read and write, so it belongs on a server. Call GitLoom from your backend, and let your backend decide what a browser may ask for.

What GitLoom does not do yet

Listed so you can plan around them rather than discover them:

Reproducing these numbers

The latency figures come from the deployed stack, driven over 200–250 authenticated requests against a 240-memory fixture, and read back from the same Gitloom/Retrieval metrics the production p99 alarm watches. Ingestion timings come from the worker's own logs. Nothing on this page is an estimate presented as a measurement; where a figure is a range rather than a number, it is written as one.