Skip to main content

Runtime quickstart

To coordinate coding agents, use the AgentD quickstart. To build an app in the browser, use App Builder. This guide installs Runtime for applications; AgentD's task orchestration does not require it.

Set up the LibraOS Runtime​

Install the runtime, configure a database and model endpoint, obtain a token, and create an agent to call. You need PostgreSQL, access to a supported model endpoint (hosted or local), and Python 3 for the token-extraction command. The binary does not include model weights or a database.

1. Install — about a minute​

curl -fsSL https://libraos.com/install.sh | sh

The installer detects your OS and architecture, downloads the matching binary, verifies it against SHA256SUMS, and installs it as libraos on your PATH.

On an air-gapped machine, download the binary and checksums from the download page on a trusted machine, verify, then copy them in:

sha256sum -c SHA256SUMS --ignore-missing
chmod +x libraos-linux-amd64
sudo mv libraos-linux-amd64 /usr/local/bin/libraos

Check it:

libraos --version

Running in Docker or Kubernetes instead? Skip to Run it as a container — the configuration is the same, but the container needs one flag the binary does not.

2. Configure and start — about three minutes​

export LIBRA_OS_PUBLIC_URL=http://localhost:8900
export LIBRA_OS_ADMIN_EMAIL=...
export LIBRA_OS_ADMIN_PASSWORD=...
export LIBRA_OS_DATABASE_URL='postgres://...'
export OPENAI_API_BASE=https://api.libraos.com/v1
export OPENAI_API_KEY="lk_..."
export OPENAI_MODEL='provider/model-served-by-your-gateway'
export LIBRA_OS_JWT_SECRET=$(openssl rand -base64 48)

# A credential for this quickstart (step 3 uses it):
export Q_SECRET=$(openssl rand -hex 24)
export LIBRA_OS_SERVICE_CLIENTS="quickstart:admin=$Q_SECRET"

libraos serve

Replace the sample credentials, database URL, and model ID before starting. Run the server in this terminal and issue requests from a second terminal; make the required request variables available there as well.

OPENAI_API_BASE can be any supported OpenAI-compatible endpoint. The example uses the LibraOS model API: create an lk_ key on console.libraos.com → API keys, and pick a model name from the price list there, such as anthropic/claude-opus-5-5. Use is billed in credits or a Token Plan. Another OpenAI-compatible gateway, or a fully local server, works the same way: see Local models for the Ollama configuration.

LIBRA_OS_JWT_SECRET signs every session and API token the server issues. Generate it once, store it with your other secrets, and reuse the same value across restarts and replicas — a new secret invalidates every existing login and token. With auth enabled, the server refuses to start if the secret is unset, left at a known default, or shorter than 16 bytes.

To check the whole setup — environment, database, LLM gateway, and the running server — use the built-in audit:

libraos doctor deployment

3. Mint a token — about a minute​

The quickstart client you registered in the environment can exchange its secret for a bearer token:

TOKEN=$(curl --fail-with-body -sS -X POST http://localhost:8900/oauth/token \
-H 'content-type: application/json' \
-d "{\"grant_type\":\"client_credentials\",\"client_id\":\"quickstart\",\"client_secret\":\"$Q_SECRET\"}" \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')

Export the token and URL in the terminal where you will run the examples:

export LIBRA_OS_URL=http://localhost:8900
export LIBRA_OS_API_KEY="$TOKEN"

If token exchange fails, check the response and service-client configuration.

note

quickstart:admin is a full-admin credential — fine for a laptop, not for a deployment. In production, register least-privilege service clients and let people sign in as themselves; see Deployment and Security.

4. Create and call your own agent​

Create a standalone persona so the example does not depend on bundled agents:

curl --fail-with-body -sS "$LIBRA_OS_URL/v1/agents" \
-H "Authorization: Bearer $LIBRA_OS_API_KEY" \
-H 'anthropic-beta: managed-agents-2026-04-01' \
-H 'Content-Type: application/json' \
-d '{"name":"quickstart-assistant","agent_type":"persona","system":"Answer concisely and explain what information you need."}'

Save the returned id. For this name the ID is quickstart-assistant. If it already exists, reuse it or choose another name. Call it:

curl --fail-with-body -sS "$LIBRA_OS_URL/agents/v1/quickstart-assistant/chat" \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{"message":"In one sentence, what can you help with?"}'

A native-chat response has this shape (answer text, model, and grounding values vary with your configuration):

{
"response": "I can help with business questions — summaries, comparisons, and reports across financials, operations, and strategy.",
"conversation_id": "chat_4f1c9a0e7b2d4c6a8e3f5b1d9c7a2e60",
"agent_id": "quickstart-assistant",
"model": "anthropic/claude-opus-5-5",
"retrieved_chunks": [],
"cited_sources": [],
"grounding": "retrieval_not_attempted",
"usage": { "...": "..." }
}

Two fields to notice:

  • retrieved_chunks is empty and grounding says so (retrieval_not_attempted: no knowledge search ran) — you haven't given it any knowledge yet. Once you ingest documents and bind them to an agent (Workspaces & memory), this same call answers from your sources and lists the exact files each answer drew on. That list is the difference between an AI that sounds right and one you can check.
  • conversation_id — pass it back on the next call to continue the conversation.

You have created an executable agent and called it through the native API. An employee record is optional shared configuration; it is not required to make this request. See the object model and API formats.

Run it as a container​

The steps above install a binary. The published image runs the same server — ghcr.io/libraos/libraos, entrypoint libraos serve, listening on 8900.

Pin a tag from releases. :latest moves only on production cutovers (vX.Y.Z); weekly tags (vX.Y.Z-week-YYYY-MM-DD) never move it, and weeklies are built for linux/amd64 only — production tags carry linux/arm64 as well, which matters if your nodes are arm.

--ulimit memlock=-1 is required, not optional

Without it the server refuses to start and exits 1:

memlock: RLIMIT_MEMLOCK soft cap is 65536 bytes, below the 8388608-byte minimum

Docker's default memlock cap is 64 KiB. Under memcg accounting the kernel charges every epoll_ctl(EPOLLET) item against that cap, and Go's netpoll uses EPOLLET for every file descriptor — so a non-trivial request load exhausts it and the process dies inside the runtime rather than returning an error. The guard refuses to boot instead of letting that happen.

Docker​

docker network create libraos-net

docker run -d --name libraos-pg --network libraos-net \
-e POSTGRES_USER=libraos -e POSTGRES_PASSWORD='<strong-password>' \
-e POSTGRES_DB=libraos \
-v libraos-pgdata:/var/lib/postgresql/data \
pgvector/pgvector:pg17

docker run -d --name libraos --network libraos-net -p 8900:8900 \
--ulimit memlock=-1 \
-e LIBRA_OS_PUBLIC_URL=http://localhost:8900 \
-e LIBRA_OS_ADMIN_EMAIL='you@example.com' \
-e LIBRA_OS_ADMIN_PASSWORD='<12+ chars>' \
-e LIBRA_OS_JWT_SECRET='<openssl rand -base64 48>' \
-e LIBRA_OS_DATABASE_URL='postgres://libraos:<strong-password>@libraos-pg:5432/libraos?sslmode=disable' \
-e OPENAI_API_BASE=https://api.libraos.com/v1 \
-e OPENAI_API_KEY='<your lk_ key>' \
-e OPENAI_MODEL='<a model your gateway serves>' \
-v libraos-runtime:/app/data/agents/_runtime \
"ghcr.io/libraos/libraos:<tag>"

curl -fsS http://localhost:8900/health

From here, steps 3 and 4 above are unchanged — mint a token, create an agent.

Two volumes that are not optional​

/app/data/agents/_runtime. Agents created through POST /v1/agents are written here as Markdown files. Without a volume they exist only in the container's writable layer, so every agent you create through the API disappears on the next docker run. The image ships its bundled agents at /app/data/agents/; only the _runtime subdirectory needs to persist.

Postgres data. Conversations, settings, audit and memory live in the database, not in the container.

LIBRA_OS_JWT_SECRET must also be the same value across restarts — regenerate it and every outstanding token stops working.

Publish on loopback when only this host calls it​

-p 8900:8900 listens on every interface of the host. When the only clients run on the same machine (a local app, a coding agent, a CLI), publish on loopback instead:

-p 127.0.0.1:8900:8900 \

Installing apps​

An app is a folder with a nova-app.yaml (see the personal assistant guide for one). The server looks for apps under LIBRA_OS_APPS_ROOT, by default /var/nova-os/apps inside the container. Mount a host directory there so you can add apps without rebuilding the image:

-v "$HOME/libraos-apps:/var/nova-os/apps" \
  • Each app is a subdirectory named after the app: $HOME/libraos-apps/<name>/nova-app.yaml.
  • On startup the server registers every app it finds there.
  • To add or update one while the server runs, copy the folder in and call POST /v1/apps/<name>/install as an admin. It registers the app at once; no restart is needed. POST /v1/apps/<name>/reload re-reads an installed app, and POST /v1/apps/<name>/uninstall archives it.
  • Without the mount, apps live in the container's writable layer and are gone on the next docker run.

A model on the same host​

Inside the container, 127.0.0.1 is the container, not your machine. To use a model server running on the host (Ollama, vLLM, a local gateway):

  • On Linux, add --add-host=host.docker.internal:host-gateway to docker run. Docker Desktop on macOS and Windows provides host.docker.internal already.
  • Point the server at it, for example -e OPENAI_API_BASE=http://host.docker.internal:11434/v1. The same applies to LIBRA_OS_EMBEDDING_API_BASE and LIBRA_OS_OLLAMA_URL.
  • The model server must listen on an address the container can reach. One that binds only 127.0.0.1 refuses the connection; bind it to the Docker bridge address or 0.0.0.0 behind a host firewall.

Kubernetes​

The same three requirements apply: the memlock limit, a persistent volume for runtime agents, and a stable JWT secret from a Secret rather than the manifest.

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: libraos
spec:
serviceName: libraos
replicas: 1
selector:
matchLabels: { app: libraos }
template:
metadata:
labels: { app: libraos }
spec:
securityContext:
# The memlock guard above. Without IPC_LOCK the pod CrashLoopBackOffs
# on the same RLIMIT_MEMLOCK error as Docker.
capabilities:
add: ["IPC_LOCK"]
containers:
- name: libraos
image: ghcr.io/libraos/libraos:<tag>
ports:
- containerPort: 8900
envFrom:
- secretRef: { name: libraos-secrets }
env:
- name: LIBRA_OS_PUBLIC_URL
value: https://libraos.example.com
- name: OPENAI_API_BASE
value: https://api.libraos.com/v1
volumeMounts:
- name: runtime-agents
mountPath: /app/data/agents/_runtime
readinessProbe:
httpGet: { path: /health, port: 8900 }
initialDelaySeconds: 5
livenessProbe:
httpGet: { path: /health, port: 8900 }
initialDelaySeconds: 30
volumeClaimTemplates:
- metadata:
name: runtime-agents
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests: { storage: 1Gi }

LIBRA_OS_JWT_SECRET, LIBRA_OS_ADMIN_PASSWORD, LIBRA_OS_DATABASE_URL and OPENAI_API_KEY belong in the libraos-secrets Secret, not in the manifest.

A StatefulSet with one replica, deliberately. Runtime agents are files on a ReadWriteOnce volume, so a second replica would neither see agents created by the first nor be able to mount the same claim on most storage classes. Scale horizontally only after moving agent definitions into the database or onto shared storage — and note that LIBRA_OS_INSTANCE_ID (defaulting to <hostname>-<pid>) is what marks orphaned async jobs at startup, so several replicas sharing one database need distinct values.

Bring your own Postgres. Use a managed instance or an operator; the single-container Postgres above is for a laptop, not a cluster.

What this basic setup does not include​

Document retrieval also needs a configured embedding endpoint/model and a compatible knowledge store. The v0.2.2 Cookbook uses Postgres with pgvector; SurrealDB is not required for that setup. The database image above includes pgvector, but that alone does not configure embeddings or enable document indexing. A misconfigured embedding model makes every ingest fail; see Embeddings. Follow Knowledge & retrieval and check GET /api/capabilities. Run the deployment check inside the container:

docker exec libraos libraos doctor deployment

What's next​

  • Cookbook & learning paths — minimal Runtime examples, independent capability branches and complete applications.

  • Build on LibraOS — the SDK, the API contract and the CLI. Start here if you are writing an application against a deployment.

  • Create your first agent — the same thing from the Python SDK, with response parsing and conversation history.

  • Define employees in YAML — the filesystem-canonical format for shared employee defaults, prompts, tools, and knowledge bindings.

  • Model settings — routing tiers, local models, embeddings, web search.

  • LibraOS model API and Credits and billing — keys, model names, credits and the Token Plan.

  • Deploy LibraOS for your team — on-prem, VPC, or air-gapped, plus the secrets checklist.

  • Security — the model to review before connecting your knowledge base.