Skip to content

Getting started

Stand up Felix locally, send your first request, then build an agent of your own. Felix is a self-hostable agents harness: the primitives and the runtime to build any agent, for any agentic workflow. You describe an agent as a YAML manifest (apiVersion: felix/v1), and Felix compiles it into a runnable agent with governance, durable execution, and multi-protocol surfaces. The bundled manifests are worked examples of those primitives, not the product.

For the mental model, read Concepts after this. For production deploy shape, see Deploy.

  • Docker with Compose v2
  • uv. It fetches Python 3.14 itself.
  • A model key in .env. A manifest that names no model runs on claude-sonnet, so FELIX_ANTHROPIC_API_KEY is the short path. With only FELIX_OPENAI_API_KEY, also set FELIX_DEFAULT_MODEL_ID=gpt-4.1. A Cloudflare Workers AI token and account id serve open-weight models.
  1. Clone and bootstrap

    git clone https://github.com/felix-run/felix.git && cd felix
    make bootstrap

    make bootstrap checks for the tools above. It writes .env with generated POSTGRES_PASSWORD and MINIO_ROOT_PASSWORD, installs the lean core, and adds the pre-commit hook. .env’s FELIX_DATABASE_URL reads ${POSTGRES_PASSWORD}, so the password is set in one place. Add your model key to .env now.

  2. Start the lean stack

    make up

    Brings up api (:8080), worker, scheduler, Postgres+pgvector, and Valkey. A one-shot migrate service applies the schema before the others start, so there is no separate migration step. Object store defaults to fs under /data (no MinIO required).

    Small hosts: make up-lite. MinIO + S3 extras: make up-full. make help lists every target.

  3. Health check

    curl -s http://localhost:8080/health | jq
    curl -s http://localhost:8080/ready | jq
    make doctor # from a uv-synced checkout, or: docker compose exec api felix doctor

    /health says the process is up, and nothing more — it does no I/O, so it reads ok even with the database down. /ready is the one that probes the dependencies:

    { "status": "ok", "env": "…", "version": "…", "multi_region": false, "federation": null }
    { "status": "ready", "checks": { "database": { "ok": true, "duration_ms": 3 }, "redis": { … }, "object_store": { … } } }

    A 503 from /ready with "status": "not_ready" names the failing check.

  4. Export your local API key

    make up runs scripts/dev-key.sh, which writes an API key into .env on first run and prints it. The Compose stack is authenticated by default and publishes on 127.0.0.1.

    export FELIX_KEY=$(grep -o 'sk-felix-local-[a-f0-9]*' .env | head -1)

The bundled default manifest is quick.

curl -s -X POST http://localhost:8080/chat \
-H "authorization: Bearer $FELIX_KEY" \
-H 'content-type: application/json' \
-d '{
"manifest": "quick",
"messages": [{ "role": "user", "content": "What is 7 * 6?" }]
}' | jq

The reply is the final message. The response also carries the full messages list, the thread_id and model the turn ran on, the thread’s leaf_id, and any approvals the run raised:

curl -s … | jq 'keys'
# ["approvals", "final", "leaf_id", "messages", "model", "thread_id"]

Pass thread_id as a suffix (no : / #); the server prefixes the tenant id:

curl -s -X POST http://localhost:8080/chat \
-H "authorization: Bearer $FELIX_KEY" \
-H 'content-type: application/json' \
-d '{
"manifest": "quick",
"thread_id": "session-1",
"messages": [{ "role": "user", "content": "And times 10?" }]
}' | jq

Streaming:

curl -N -X POST http://localhost:8080/chat/stream \
-H "authorization: Bearer $FELIX_KEY" \
-H 'content-type: application/json' \
-d '{"manifest":"quick","messages":[{"role":"user","content":"Say hi."}]}'

model is a Felix manifest name (GET /v1/models):

curl -s -X POST http://localhost:8080/v1/chat/completions \
-H "authorization: Bearer $FELIX_KEY" \
-H 'content-type: application/json' \
-d '{
"model": "quick",
"messages": [{ "role": "user", "content": "Say hi." }]
}' | jq

An agent is a manifest. Keep yours outside the clone, for example ~/felix-agents/triage.yaml:

# yaml-language-server: $schema=https://raw.githubusercontent.com/felix-run/felix/main/schemas/manifest.schema.json
apiVersion: felix/v1
kind: Agent
metadata:
name: triage
description: Sorts incoming requests into bug, question or feature, and says why.
spec:
pattern: react
system_prompt:
inline: |
You triage incoming requests. Answer with one of bug, question or feature,
then one sentence on why.
tools: [] # built-ins, MCP servers and your own plugin tools go here
auth:
inbound:
allow_anonymous: true # local development only; drop it once callers carry a key

Validate it, then store it in the running stack. PUT /manifests/{name} takes the manifest as JSON under manifest and needs the manifests:write scope, which the local key has. Validating first runs the same refusals the write does:

uv run felix validate-manifest ~/felix-agents/triage.yaml
uv run python -c 'import json, sys; from ruamel.yaml import YAML; print(json.dumps({"manifest": YAML(typ="safe").load(open(sys.argv[1]))}))' \
~/felix-agents/triage.yaml \
| curl -s -X PUT http://localhost:8080/manifests/triage \
-H "authorization: Bearer $FELIX_KEY" -H 'content-type: application/json' -d @- | jq '.version'
curl -s -X POST http://localhost:8080/chat \
-H "authorization: Bearer $FELIX_KEY" -H 'content-type: application/json' \
-d '{"manifest":"triage","messages":[{"role":"user","content":"The export button does nothing"}]}' | jq '.final'

Each PUT stores a new version without a restart. GET /manifests/triage/versions lists them, and the management API covers canary and rollback. Running the API from the checkout instead (make db migrate, then FELIX_MANIFESTS_DIR=~/felix-agents make dev) serves the directory directly, with no PUT.

The same manifest grows by adding primitives: tools, skills, memory, MCP servers and A2A peers, sub-agents and delegation, approvals and guardrails, execution.mode: durable, and a different pattern. The manifest reference covers every field, and the harness repo’s manifests/governed.yaml uses most of them. For a tool, pattern, model provider or store of your own, write a plugin. examples/felix-plugin-example in the harness repo registers one of every seam.

Run the chat UI against this harness

From felix-run/web, with Compose Felix already on :8080:

git clone https://github.com/felix-run/web.git felix-web && cd felix-web
pnpm install
pnpm chat:dev # Vite :5173, proxies /api → http://127.0.0.1:8080
Run an offline eval (CI, no API keys)
uv sync --dev
FELIX_DATABASE_URL=memory://local FELIX_OBJECT_STORE=memory FELIX_ALLOW_INSECURE=true \
uv run felix eval -d smoke -m quick -f fixtures/eval/smoke.json --mock