FOR AI · 02

Agent quickstart

Five steps, all runnable, all on port 9200: boot XERJ → check it is up → store a memory → recall it by meaning → run a semantic search. Every request and every response below is a real run against a freshly booted, empty XERJ — no seeding, no external embedding service, no API key. If you have curl, you have everything.

0 · Boot XERJ

One binary. --insecure disables TLS and API-key auth so you can talk to it over plain HTTP on the default ES-compat port 9200 — perfect for a local agent loop.

# from a release build (see /docs/install.html for downloads)
$ ./engine/target/release/xerj --insecure --data-dir /tmp/xerj-agent &

New to XERJ? The install page has one-line downloads for every platform.

1 · Confirm it is up

The root path returns the ES-style info document. Any Elasticsearch client uses this as its handshake — which is exactly why an off-the-shelf ES client library points at XERJ unchanged.

$ curl -s localhost:9200/
{"name":"local","cluster_name":"xerj",
 "cluster_uuid":"xerj-cluster-0000-0000-0000-000000000000",
 "version":{"number":"8.13.0","build_flavor":"default","build_type":"tar",
            "lucene_version":"9.10.0", ... },
 "tagline":"You Know, for Search"}

Want the cluster's health instead? GET /_cluster/health returns {"status":"green", ...} on a healthy single node.

2 · Store a memory

An agent learns something worth keeping. Store it in a per-agent namespace — here agent-demo. You do not create the namespace first; it is created on the first store. You do not supply a vector; because the memory's text is a semantic_text field, XERJ auto-embeds it with its built-in embedder at store time.

$ curl -sXPOST localhost:9200/_memory/agent-demo \
    -H 'content-type: application/json' \
    -d '{
      "text": "The user prefers metric units and a dark UI theme.",
      "metadata": {"kind": "preference"}
    }'
{"created":true,"id":"77eff57b-e432-431c-8b49-8a16b33ab551","namespace":"agent-demo"}

Store a second one so recall has something to rank against:

$ curl -sXPOST localhost:9200/_memory/agent-demo \
    -H 'content-type: application/json' \
    -d '{"text":"The production database is Postgres 16 in us-east-1.","metadata":{"kind":"fact"}}'
{"created":true,"id":"ddaecb5f-61ab-4b57-bc31-82a118e6131c","namespace":"agent-demo"}

3 · Recall by meaning

On a later turn the agent asks a question that shares no keywords with the stored memory. Set "semantic": true and pass the raw question as query: XERJ embeds it with the same built-in embedder used at store time and ranks memories by vector similarity.

$ curl -sXPOST localhost:9200/_memory/agent-demo/_recall \
    -H 'content-type: application/json' \
    -d '{
      "query": "what display settings does the user like?",
      "semantic": true,
      "k": 2
    }'
{"hits":[
   {"id":"77eff57b-e432-431c-8b49-8a16b33ab551",
    "metadata":{"kind":"preference"},
    "score":0.655571460723877,
    "text":"The user prefers metric units and a dark UI theme."},
   {"id":"ddaecb5f-61ab-4b57-bc31-82a118e6131c",
    "metadata":{"kind":"fact"},
    "score":0.5951915979385376,
    "text":"The production database is Postgres 16 in us-east-1."}],
 "namespace":"agent-demo"}

The UI-preference memory ranks first for a question about "display settings" it shares no words with. Two other recall modes use the same endpoint:

4 · Semantic search over your own documents

Memory is one store; your own indices are another. Give a field the semantic_text type and XERJ auto-embeds it on ingest into a companion vector, so a semantic query retrieves by meaning. First create the index:

$ curl -sXPUT localhost:9200/notes \
    -H 'content-type: application/json' \
    -d '{"mappings":{"properties":{"body":{"type":"semantic_text"}}}}'
{"acknowledged":true,"shards_acknowledged":true,"index":"notes"}

Index a few documents. Note the verbs: an explicit id uses PUT /notes/_doc/{id}; an auto id uses POST /notes/_doc. (Sending POST to an explicit-id path is the one easy mistake — it will not index.)

$ curl -sXPUT localhost:9200/notes/_doc/1 -H 'content-type: application/json' \
    -d '{"body":"How do I roll back a bad deployment?"}'
$ curl -sXPUT localhost:9200/notes/_doc/2 -H 'content-type: application/json' \
    -d '{"body":"Reverting a broken release to the previous version."}'
$ curl -sXPUT localhost:9200/notes/_doc/3 -H 'content-type: application/json' \
    -d '{"body":"Configuring TLS certificates for the ingress."}'
$ curl -sXPOST localhost:9200/notes/_refresh

Now search by meaning. The query text "undo a failed rollout" shares no words with any document, yet the rollback note wins:

$ curl -sXPOST localhost:9200/notes/_search \
    -H 'content-type: application/json' \
    -d '{
      "query": {"semantic": {"field": "body", "query": "undo a failed rollout", "k": 2}},
      "_source": ["body"]
    }'
{"hits":{"total":{"value":2,"relation":"eq"},
  "hits":[
    {"_id":"2","_score":0.5531777143478394,
     "_source":{"body":"Reverting a broken release to the previous version."}},
    {"_id":"1","_score":0.53...,
     "_source":{"body":"How do I roll back a bad deployment?"}}]}}

The "Configuring TLS certificates" note is correctly excluded. Filter _source to the fields you want — the auto-generated companion embedding lives in a body_vector field and is otherwise part of _source, so scoping it keeps responses small.

Going further — vector & hybrid

If you already have embeddings, add a dense_vector field and run exact knn; to combine keyword and vector recall in a single call, use a hybrid query with rrf fusion. Both are shown, with response shapes, on the endpoint contract. For worked, end-to-end walkthroughs see the recipes: agent memory, semantic search & RAG, hybrid search.

Clean up

Drop the memory namespace and the demo index when you are done:

$ curl -sXDELETE localhost:9200/_memory/agent-demo
{"dropped":true,"namespace":"agent-demo"}
$ curl -sXDELETE localhost:9200/notes
{"acknowledged":true}