Architecture
How a request flows through Felix end-to-end. Source of truth: felix-run/felix.
Process topology
Section titled “Process topology”Client → Ingress (Caddy / Traefik / nginx / CDN) │ ▼ felix-api (CPython 3.14, Granian, FastAPI) ─┐ felix-worker (Taskiq consumer) ─┤ no ingress felix-scheduler (Taskiq cron enqueue) ─┤ no ingress ▼ Postgres+pgvector · Valkey · object store (fs | s3 | gcs | memory)In words: requests reach an ingress (Caddy, Traefik, nginx or a CDN), which routes to
felix-api only. felix-worker and felix-scheduler run alongside it with no ingress of their
own. All three connect to the same Postgres with pgvector, Valkey, and object store (fs, s3,
gcs or memory).
| Process | Role |
|---|---|
API (apps/api) |
HTTP surfaces, OpenAPI//docs, agent invoke/stream |
Worker (apps/worker) |
Audit flush, jobs, memory consolidation, retention, anomaly, continuous eval, fiber resume |
| Scheduler | Enqueues labeled Taskiq cron tasks (required alongside the worker) |
The harness library lives in packages/harness (felix). The CLI is apps/cli (felix migrate|doctor|eval|mint-jwt|…).
Felix is plain CPython in containers you run. There is no serverless or edge runtime in the stack, and no vendor-specific compute or storage binding — Postgres, Valkey and an object store sit behind Protocols. Deploy with Compose or Helm — see getting started and deploy.
HTTP surfaces
Section titled “HTTP surfaces”create_app (apps/api) mounts:
| Surface | Paths |
|---|---|
| Chat | POST /chat, /chat/stream, /chat/steer, /chat/fork, /chat/rewind, GET /chat/runs/{resume_token} |
| OpenAI-compatible | POST /v1/chat/completions, GET /v1/models |
| A2A | POST /a2a |
| MCP | POST /mcp |
| Discovery | GET /.well-known/agent-card.json, JWKS when configured |
| Management | /audit, /approvals, /plans, /jobs, /manifests, /eval, /usage, /memory, /documents, /artifacts |
| Internal | POST /internal/* (consumer secret when auth ≠ none) |
Interactive OpenAPI UI: a running harness serves Scalar at /docs. Swagger and ReDoc are both off,
so there is one reference surface under one CSP — and it is authenticated unless
FELIX_DOCS_PUBLIC=true. The public copy is this site’s /reference/, built from the
openapi.json attached to the release production runs.
The manifest write verbs (PUT, canary, rollback, clear-canary) are not registered at all under
FELIX_MANIFEST_SOURCE=bundled, so they are absent from the OpenAPI document rather than present
and refusing.
Middleware chain
Section titled “Middleware chain”Order on every request, outermost first:
- Request id — every response carries
x-request-id, including one rejected downstream. - Security headers — on every response, a 413 and a 401 included.
- Body limit — reject oversized payloads before any other work.
- Rate limit — keyed by tenant; skips only the probes (
/health,/live,/ready) and/.well-known/*. - Auth —
none/api_key/jwt(see auth); installsRequestContext(tenant, scopes,LimitState).
Rate limit is outside auth, and that is the point. Registered the other way round, a 401
returned before the limiter was consulted and credential guessing was entirely unthrottled. For
the same reason /docs, /openapi.json and /metrics are not exempt: under api_key / jwt
those were three more paths where a credential could be guessed at no cost.
Self-authenticating mounts (e.g. /internal) skip bearer parsing; the router enforces its own credential (FELIX_CONSUMER_SHARED_SECRET).
Chat request lifecycle
Section titled “Chat request lifecycle”POST /chat or /v1/chat/completions → auth + rate limit → resolve_tenant_manifest (tenant Postgres → object store → bundled) → prepare_tenant_invoke enforce_inbound_auth (spec.auth.inbound: anonymous / required scopes) ensure_thread_pin (spec.governance.pin_compile → 409 if the pointer moved) → apply_inbound_screening on the caller's turn → build_agent(manifest, deps) validate → system prompt + skills bind MCP / peers / sandboxes / containers / queues / browser governance wrappers (secret masking → … → approvals) get_pattern(spec.pattern) → Agent optional durable wrap (fibers) → agent.invoke / agent.stream_events session strategy renders working set model chat → tool dispatch → persist session eventsNames are the actual callables: felix.runtime owns resolve_tenant_manifest and
prepare_tenant_invoke, felix.manifests.builder owns build_agent, and invoke /
stream_events are the two methods on the Agent protocol in felix.patterns.types.
Durability
Section titled “Durability”The durability backend is fibers (Postgres-backed sleep/step/resume; each worker polls for due fibers every second and advances up to eight at once, with a once-a-minute cron sweep as the backstop). Set spec.execution.mode: durable to enqueue and return 202 with a resume_token.
Background work
Section titled “Background work”felix-scheduler enqueues labeled cron tasks; felix-worker consumes them. Run both, or none of
the following ever fires:
Task (apps/worker) |
Cron | Does |
|---|---|---|
flush_audit |
*/1 * * * * |
Drain the audit buffer to Postgres (and the optional warehouse spill) |
flush_usage |
*/1 * * * * |
Drain buffered token usage |
run_scheduled_jobs |
* * * * * |
Sweep due jobs rows |
fiber_scheduler |
* * * * * |
Wake and resume due durable fibers |
webhook_delivery |
* * * * * |
POST finished durable runs to their completion webhooks, with retry |
skill_jobs |
* * * * * |
Run accepted skill improvements (each into a draft) and queued skill evaluations, at most five of each per sweep |
continuous_eval |
*/10 * * * * |
Continuous eval tick |
skill_upstream_checks |
*/10 * * * * |
Check up to 50 imported skills due under FELIX_SKILL_IMPORT_CHECK_HOURS against their origins; off at 0 |
skill_update_notifications |
* * * * * |
Send queued skill.update_available events to the endpoints FELIX_SKILL_UPDATE_WEBHOOKS binds to each tenant: up to 50 a tick, at most 10 of one tenant’s, one sweep at a time on its own skill_job_lease row |
consolidate_memory |
*/15 * * * * |
Memory consolidation |
anomaly_scan |
*/30 * * * * |
Error-rate scan and canary auto-rollback |
retention_sweep |
0 3 * * * |
Prune expired rows and objects |
Package map
Section titled “Package map”| Path | Concern |
|---|---|
packages/harness/src/felix/ |
Manifests, patterns, tools, session, governance, auth, plugins |
apps/api |
FastAPI composition + routes |
apps/worker |
Taskiq worker and scheduler entrypoints |
apps/cli |
Ops CLI |
manifests/ |
Bundled agents (quick, deep, router, …) |